Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Java Authentication and Authorization Service

Panoramica, ed esempio pratico, sulle Java Authentication and Authorization Service
Panoramica, ed esempio pratico, sulle Java Authentication and Authorization Service
Link copiato negli appunti

L'aspetto della sicurezza di un sistema informatico, sia esso in ambiente distribuito, sia esso un'applicazione desktop, viene troppo spesso trascurato da chi si dovrebbe occupare di implementare servizi che siano efficienti e sicuri. Tante volte si sottovaluta l'impatto, tante volte ci si occupa in maniera sommaria della questione adottando tecniche difficilmente manutenibili. Molto spesso vengono alla ribalta notizie di violazione dei sistemi informatici e la questione della sicurezza viene apprezzata tantissimo dalle aziende soprattutto in termini di investimenti. Portare informazioni sensibili su un media come Internet e non preoccuparsi di gestire efficientemente le autorizzazioni diventa una situazione altamente rischiosa, soprattutto considerando che si moltiplicano il numero di persone che attentano ai sistemi informatici, non più per semplice diletto, come avveniva un tempo, ma per rubare e rivendere dati sensibili.

Quando parliamo di autenticazione, soprattutto in ambito di applicazioni Web based, ci viene in mente l'associazione login e password e al limite un certificato per criptare i dati in ingresso e uscita dall'applicazione. In un semplice applicativo gestionale la gestione customizzata di questa funzione potrebbe essere sufficiente; ma come comportarsi in situazioni più complesse, dove, accanto all'autenticazione esistono diversi livelli di autorizzazione? Nell'articolo su SSL abbiamo imparato a criptare le informazioni, mantenendo quindi un grado di confidenzialità elevato all'esterno dell'applicazione. Ma come gestire in maniera efficiente i livelli interni di autorizzazione?

A partire dalla versione 1.4 della piattaforma Java, un prezioso strumento è stato integrato per rispondere alle domande poste in precedenza. Con JAAS (Java Authentication and Authorization Service) viene gestita la realizzazione di componenti adibiti esclusivamente alla gestione degli accessi alle risorse, rimanendo interamente indipendente dalla realizzazione concreta.

Questo servizio è alla base della gestione della sicurezza degli application server e dei componenti di logica applicativa (EJB in particolare) su di esso installati. Vedremo in questo primo articolo la parte dedicata all'autenticazione, mentre ci occuperemo della gestione delle autorizzazioni in un secondo momento.

Autenticazione e autorizzazione sono due differenti concetti che è bene distinguere. Seppure piuttosto semplice da capire, l'implementazione di JAAS è abbastanza criptica. Il perché è legato al fatto che JAAS vuole mantenere l'indipendenza tra la funzione da effettuare e la reale implementazione concreta.

Significa che i concetti di sicurezza sono astratti e l'implementazione è legata al particolare meccanismo di sicurezza che si utilizzerà di volta in volta. In questo modo, JAAS, si presenta come un modulo "pluggable" che permette di modificare e di utilizzare diversi meccanismi di sicurezza mantenendo intatto il codice client utilizzato.

Vediamo il set di API che utilizzeremo con l'aiuto del diagramma: le classi in bianco rappresentano quelle facenti parte del set di JAAS.

Figura 1. Architettura JAAS
Architettura JAAS

La classe client è qui rappresentata dalla classe MainAuthentication. Quest'ultima utilizza la classe LoginContext che espone un metodo login() (gestisce tutto il ciclo di vita del processo di autenticazione, quindi, è l'unico metodo che il client invoca).

L'interfaccia CallbackHandler rappresenta il modo con cui il sistema richiede le credenziali d'accesso all'utente finale. A noi quindi il compito di creare un'implementazione per questa classe.

L'interfaccia LoginModule rappresenta il componente che implementa in maniera concreta l'autenticazione realizzando i metodi esposti. Infatti la classe CustomLoginModule, che dovremo implementare, verrà popolata dal riferimento al CallbackHandler (utilizzato per chiedere all'utente le credenziali d'accesso) e realizzerà concretamente l'autenticazione accedendo al layer su cui l'autenticazione si vuole basare (un database, un sistema proprietario, ecc).

La classe LoginContext viene configurata attraverso la presenza di un file di configurazione con cui esplicitiamo il LoginModule concreto da utilizzare (cioè CustomLoginModule). In questo modo, cambiando la logica concreta di autenticazione (per esempio con un altro LoginModule) bisognerà modificare il solo descrittore.

Altre classi non presenti nel diagramma sono:

  • Subject: rappresenta un'entità che contiene le informazioni su un soggetto (per esempio un utente).
  • Principal: rappresenta un'informazione specifica che può indentificare il soggetto (per esempio il codice fiscale). Un Subject è un aggregato di Principal (che ne mantiene il riferimento con una Collection Set).

Il metodo commit(), che nel ciclo di vita rappresenta l'andata a buon fine dell'autenticazione, si occuperà di valorizzare il Subject con il Principal identificato.

Esempio autenticazione

Cerchiamo di essere più chiari attraverso l'esempio che ci mostra come effettuare un'autenticazione JAAS. Ovviamente è a scopo didattico, quindi utilizzeremo come autenticazione una semplice coppia login-password embedded nel codice. Il concetto tuttavia rimane lo stesso, essendo la logica astratta per definizione.

La prima classe che vediamo è il client. L'idea è semplice: effettuiamo il login e vediamo se esso sia andato a buon fine.

Listato 1. Una porzione di codice che eseguiremo dopo aver effettuato una procedura di autenticazione JAAS

package it.html.jaas;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

public class MainAuthentication {

  public static void main(String[] args) {
  
    //Creiamo il login context passando il riferimento
    //(JaasSample) della configurazione che vogliamo usare

    LoginContext lc = null;
    try {
      lc = new LoginContext("JaasSample", new SimpleHandler());
    } catch (Exception e) {
      System.err.println("Errore nella creazione del login context: "+e.getMessage());
      System.exit(-1);
    }
    
    //Proviamo a effettuare l'autenticazione
    try {
      lc.login();
      System.out.println("L' autenticazione ha avuto esito positivo!");
      
      Subject subject = lc.getSubject();
      System.out.println("Subject con autenticazione: "+subject);
    } catch (LoginException e) {
      System.out.println("Autenticazione fallita: "+e.getMessage());
    }
  
  }
}

La creazione del LoginContext è effettuata mediante il passaggio del nome di una configurazione (JaasSample) e di un CallbackHandler concreto. Se il login va a buon fine mostriamo l'esito positivo e il Subject che abbiamo autenticato.

Il SimpleHandler creato estende il metodo handle(), curandosi di chiedere all'utente le credenziali di accesso:

Listato 2. Gestiamo il Callback Handler, fornendo un'implementazione concreta

package it.html.jaas;

class SimpleHandler implements CallbackHandler{

  // L'unico metodo che dobbiamo implementare per gestire la richiesta delle credenziali di accesso
  
  public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
    for (int i = 0; i < callbacks.length; i++) {
      if (callbacks[i] instanceof NameCallback) {
        NameCallback nc = (NameCallback)callbacks[i];
        
        //Chiediamo all'utente l'input della username
        System.out.println("Inserisci username> ");
        String username = new BufferedReader(new InputStreamReader(System.in)).readLine();
        
        nc.setName(username);
      } else if (callbacks[i] instanceof PasswordCallback) {
        PasswordCallback pc = (PasswordCallback)callbacks[i];
        
        //Chiediamo all'utente l'input della username
        System.out.println("Inserisci password> ");
        String password = new BufferedReader(new InputStreamReader(System.in)).readLine();
        
        pc.setPassword(password.toCharArray());
      } else {
        throw new UnsupportedCallbackException(callbacks[i], "Callback di tipo sconosciuto.");
      }
    }
  }
}

La chiamata a questo metodo avverrà dal LoginModule concreto con il passaggio di un array di Callback che rappresentano la tipologia di informazioni da chiedere all'utente. In questo semplice esempio passeremo un NameCallback e un PasswordCallback.

La classe più importante è la classe LoginModule, quella che effettivamente si occupa della procedura di autenticazione:

Listato 3. Implementa e realizza un LoginModule concreto

package it.html.jaas;

public class PasswordLoginModule implements LoginModule {
  
  private CallbackHandler callbackHandler;
  
  //Variabili per mantenere il riferimento allo stato dell'autenticazione
  private boolean loginSucceeded = false;
  private boolean commitSucceeded = false;
  
  //Usate per mantenere le info dall'utente
  private String username;
  private char[] password;
  
  private Subject subject;
  private Principal principal;
  
  //Inizializzazione del componente (il metodo funge da costruttore)
  public void initialize(Subject subject,CallbackHandler callbackHandler,
  Map sharedState,Map options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.loginSucceeded = false;
    this.commitSucceeded = false;
    this.username = null;
    password = null;
  }
  ..//

Il metodo initialize (che fa parte del ciclo di vita del modulo) effettua la valorizzazione dei componenti in gioco. Di fatto si tratta di un costruttore.

Listato 4. Modulo di login

  //username: "test"
  //password: "html.it"

  
  public boolean login() throws LoginException {
    if (callbackHandler == null) {
      throw new LoginException("No CallbackHandler defined");
    }
    
    // creiamo una callback per lo username, una per la password
    Callback[] callbacks = new Callback[2];
    callbacks[0] = new NameCallback("Username");
    callbacks[1] = new PasswordCallback("Password", false);
    
    try {
      // Richiamo il metodo handle sull'handler SimpleHandler
      callbackHandler.handle(callbacks);
      username = ((NameCallback)callbacks[0]).getName();
      char[] tempPassword = ((PasswordCallback)callbacks[1]).getPassword();
      password = new char[tempPassword.length];
      System.arraycopy(tempPassword, 0, password, 0, tempPassword.length);
      // Ripuliamo la password
      ((PasswordCallback)callbacks[1]).clearPassword();
    } catch (IOException ioe) {
      throw new LoginException(ioe.toString());
    } catch (UnsupportedCallbackException uce) {
      throw new LoginException(uce.toString());
    }
    
    //Effettuiamo il controllo della password
    if (username.equals("test") && new String(password).equals("html.it")) {
      //Autenticazione corretta
      loginSucceeded = true;
      return true;
    } else {
      //Autenticazione fallita
      loginSucceeded = false;
      username = null;
      password = null;
      throw new FailedLoginException("Username e/o password errati.");
    }
  }
  ..//

Il metodo che effettua l'autenticazione è il metodo login(). Nel nostro semplice esempio creiamo l'array di callback con due elementi (login e password) e richiamiamo il metodo handle() sul CallbackHandler passato come riferimento. L'autenticazione, nel nostro caso, sarà effettuata da un operazione di confronto con delle stringhe determinate (test e html.it). In realtà potremmo effettuare un controllo su un database, su una directory LDAP... o in su qualsiasi supporto che vuole gestire il meccanismo di autenticazione.

Gli ultimi metodi sono il metodo commit(), abort() e logout():

Listato 5. Il metodo, secondo il ciclo di vita, viene richiamato dopo il login avvenuto con successo

  public boolean commit() throws LoginException {
    // Aggiungiamo il Principal (riferito a username) al subject
    principal = new PrincipalImpl(username);
    if (!(subject.getPrincipals().contains(principal))) {
      subject.getPrincipals().add(principal);
    }
    
    // Pulizia
    username = null;
    password = null;
    commitSucceeded = true;
    return true;
  }
  
  //Richiamata dal ciclo di vita del componente nel caso di fallimento del login
  public boolean abort() throws LoginException {
    if (loginSucceeded == false) {
      return false;
    } else if (loginSucceeded == true && commitSucceeded == false) {
      loginSucceeded = false;
      username = null;
      password = null;
      principal = null;
    } else {
      logout();
    }
    return true;
  }
  
  //Logout
  public boolean logout() throws LoginException {
    // Rimuoviamo il principal dal subject
    subject.getPrincipals().remove(principal);
    loginSucceeded = false;
    commitSucceeded = false;
    username = null;
    password = null;
    principal = null;
    return true;
  }
}

Il primo è successivo al login (se effettuato con successo) e si occupa di aggiungere l'identità Principal al Subject. Gli altri due metodi effettuano la cancellazione delle credenziali o indicano la fine della sessione di autenticazione.

Ultima cosa necessaria è il file di configurazione, che chiameremo jaas.conf:

Listato 6. File di configurazione

JaasSample {
  it.html.jaas.PasswordLoginModule required;
};

In cui spieghiamo che la configurazione JaasSample richiede il modulo concreto PasswordLoginModule. Interessante è notare come qui potremmo aggiungere diverse autenticazioni per rendere il nostro software più sicuro. Per esempio a una prima sessione di autenticazione mediante login e password potremmo aggiungere un modulo che effettua la lettura di una smart card e poi ancora un lettore di dati biometrici. Tutto "pluggabile" e facilmente configurabile senza intaccare minimamente il client.

Esecuzione

Per eseguire il codice è necessario definire una variabile che indichi il file di configurazione (jaas.conf) da caricare:

java -Djava.security.auth.login.config=jaas.conf it.html.jaas.MainAuthentication

Vediamo il risultato di una esecuzione con successo:

Figura 2. Esecuzione del client
Esecuzione del client

Ti consigliamo anche