Ecco una serie di appunti relativi all’autenticazione via JAAS.

Voglio realizzare una pagina di login che una volta inseriti user e password invochi un controller di login che effettua la verifica delle credenziali utilizzando l’accesso al mio server di posta mediante le API JAAS.

Vediamo quello che occorre:

File di configurazione del modulo di login

File di configurazione contenente il LoginModule da utilizzare.
Supponiamo che il file si chiami “jaas_login.conf”.
Nel mio caso questo oggetto si appoggia all’elenco degli utenti definiti sul server di posta e quindi il file sarà così strutturato:


JaasMailAuthentication { 

  it.lcianci.jaas.MailLoginModule required server=”mio_server_di_posta” protocol=”imaps” debug=”true” trustStore=”/path_certificato/truststore.jks” trustStorePassword=”xyzkt”; 
};

Il certificato è quello relativo al mio server di posta (chiedete ai vostri sistemisti).

Supposto che l’applicazione gira su tomcat, occorre passare come parametro della JVM il nome del file di configurazione nelle CATALINA_OPTS, in particolare:

-Djava.security.auth.login.config=$MIO_PATH/jaas_login.conf

La pagina di login e il suo controller

La pagina login.jsp raccogli i dati user e password e chiama un controller (servlet) che istanzia un oggetto LoginContext passando al costruttore la stringa relativa al file di configurazione che definisce il LoginModule (nel mio caso MailLoginModule) e un CallbackHandler (nel mio caso UserHandler) dove vado a salvare i miei parametri user e pwd.

Tralasciando la pagina jsp di login che è banale, ecco il codice della servlet LoginController sulla quale rigiriamo il controllo per l’autenticazione, in grassetto le righe più interessanti:

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 
  UserHandler uH = new UserHandler(); 
  uH.setUsername(request.getParameter(“username”));
  uH.setPassword(request.getParameter(“password”)); 
  LoginContext lc = null; 
  try { 
    lc = new LoginContext(“JaasMailAuthentication”, uH); 
  } catch (Exception le) { 
    System.err.println(“Cannot create LoginContext. ” + le.getMessage()); 
    System.exit(-1); 
  }
  try { if (lc != null) { 
    lc.login();
    Iterator it = lc.getSubject().getPrincipals().iterator(); 
    MailPrincipal mailPrincipal = (MailPrincipal)it.next();
    request.getSession().setAttribute(“loggedUser”, mailPrincipal);
    response.sendRedirect(request.getContextPath() + “/front”); }
  } catch (LoginException le) { 
    // Gestire gli errori di login, se necessario, e ridirigere alla pagina di login stessa 
    response.sendRedirect(request.getContextPath() + “/login”);
  }
}

Il LoginContext carica il LoginModule presente nel file di configurazione e ne invoca il metodo login(). Accertarsi di istanziare il LoginContext utilizzando la stringa usata nel file di configurazione, nel mio caso “JaasMailAuthentication”, e passando al costruttore un CallbackHandler all’interno del quale ho impostato utente e password con i dati inseriti dall’utente (lo UserHandler).

Il LoginModule

Il mio MailLoginModule, quello definito nel file di configurazione e passato al LoginContext, implementa l’interfaccia LoginModule che presenta tra gli altri i metodi initialize, commit e login.

Il metodo initialize carica i parametri letti dal file di configurazione all’interno di una Map presente nella mia classe e quindi ho accesso a tutti i parametri direttamente dal mio codice:

public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { 

  this.subject = subject; 
  this.callbackHandler = callbackHandler; 
  this.options = options; 
}
Il metodo commit, crea e salva l’oggetto Principal che poi ritroviamo nel LoginContext:

public boolean commit() throws LoginException { 

  boolean result = false; 
  userPrincipal = new MailPrincipal(username); 
  if (!subject.getPrincipals().contains(userPrincipal)) 
    subject.getPrincipals().add(userPrincipal); 
  username = null; 
  for (int i = 0; i < password.length; i++) 
    password[i] = ‘ ‘; 
    password = null; 
    result = true; 
  } 
  return result;
}
All’interno del metodo login, il LoginModule passa i parametri ricevuti al CallbackHandler e ne invoca il metodo handle().
Il metodo handle valorizza due oggetti dell’API JAAS che sono il NameCallback e il PasswordCallback.

Una volta che il CallbackHandler ritorna dal suo metodo handle, ovvero ha gestito le credenziali, posso effettuare il tentativo di autenticazione andando a connettermi con il mio particolare server, quindi quello di posta. Devo implementare la connessione e tutto il resto.

Vediamo nel complesso il metodo login di cui abbiamo parlato:

public boolean login() throws LoginException { 
  Callback callbacks[] = null; 
  String server = null; 
  String protocol = null; 
  String trustStore = null; 
  String trustStorePassword = null; 
  String ts = null; 
  String tsp = null; 
  succeeded = false;


  // Controllate che callbackHandler e options, in teoria caricati dal metodo initialize, siano effettivamente disponibili. 
  // options conterrà i vari attributi presenti nel nostro file di configurazione quindi server, protocol, trustStore… ….. 
  callbacks = new Callback[2]; 
  callbacks[0] = new NameCallback(“username: “); 
  callbacks[1] = new PasswordCallback(“password: “, false); 
  try { 
    char tmpPassword[] = null; 
    callbackHandler.handle(callbacks); 
    username = ((NameCallback) callbacks[0]).getName(); 
    tmpPassword = ((PasswordCallback) callbacks[1]).getPassword(); 
    if (tmpPassword == null) tmpPassword = new char[0]; 
    password = new char[tmpPassword.length]; 
    System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); 
    ((PasswordCallback) callbacks[1]).clearPassword(); 
  } 
  catch (IOException ioe) { 
    getLogger().warning(“Error on requesting username and password: ” + ioe.toString()); 
    throw new LoginException(ioe.toString()); 
  } 
  catch (UnsupportedCallbackException uce) { 
    getLogger().warning(“Impossible to obtain information to authenticate.”); 
    throw new LoginException(“Impossible to obtain information to authenticate.”); 
  } 
  try { 
    Properties props = new Properties(); 
    Session session = null; 
    Store store = null; 
    if (trustStore != null) { 
      ts = System.getProperty(“javax.net.ssl.trustStore”); 
      System.setProperty(“javax.net.ssl.trustStore”, trustStore); 
    } 
    if (trustStorePassword != null) { 
      tsp = System.getProperty(“javax.net.ssl.trustStorePassword”); 
      System.setProperty(“javax.net.ssl.trustStorePassword”, trustStorePassword); 
    } 
    session = Session.getDefaultInstance(props); 
    store = session.getStore(protocol); 
    store.connect(server, username, new String(password)); 
    store.close(); 
    succeeded = true; 
  } 
  catch (NoSuchProviderException e) { 
    getLogger().severe(“The protocol option property is not supported.”);  
    throw new LoginException(“Impossible to use the protocol specified.”); 
  } 
  catch (MessagingException e) { 
    getLogger().warning(“Authentication failure for (username: ” + username + “; server: ” + server + “; protocol: ” + protocol + “): ” + e.toString());
    throw new LoginException(“Authentication failure.”); 
  } 
  finally { 
    System.setProperty(“javax.net.ssl.trustStore”, ts != null ? ts : “”); 
    System.setProperty(“javax.net.ssl.trustStorePassword”, tsp != null ? tsp : “”); 
  } 
  return succeeded;
}

Se il controllo ha successo e la connessione va a buon fine, il metodo login() del LoginContext termina con successo e i dati utente sono disponibili nell’oggetto Principal o attraverso il Subject del LoginContext. Se riguardate il codice della servlet controller vedrete che ho salvato in sessione un oggetto loggedUser che è proprio quello di cui sto parlando.

Operazioni a contorno

Tomcat va lanciato con il flag “-security”.

Se servono particolari grant all’applicazione Web, occorre esplicitarle nel file

CATALINA_HOME/conf/catalina.policy

Nel mio caso ad esempio dovendo leggere un mio parametro JVM e dovendo accedere ad alcune cartelle del file system, ho impostato le seguenti grant:

grant codeBase “file:${catalina.home}/webapps/MioContext/-” { 

  // Evita di intercettare migliaia di eccezioni di sicurezza lanciate dai più disparati oggetti permission   
  java.security.AllPermission; 
  // Permette di leggere la variabile passata alla JVM come parametro permission 
  java.util.PropertyPermission “mio.config”, “read”; 
  // Imposta i permessi in lettura/scrittura ad alcune cartelle di configurazione esterne all’applicazione 
  permission java.io.FilePermission  
 “${file.separator}usr${file.separator}local${file.separator}MioContext${file.separator}config${file.separator}-“, “read”; 
  permission java.io.FilePermission 
 “${file.separator}usr${file.separator}local${file.separator}MioContext${file.separator}logs${file.separator}-“, “read, write”;
};