Introduzione

Il Single Sign-On è un processo mediante il quale un utente fornisce le proprie credenziali solo la prima volta all'accesso di un sistema e successivamente può accedere automaticamente a tutte le risorse a cui è abilitato senza ulteriori richieste di autenticazione. Per risorse si intendono tutti i servizi su qualsiasi sistema a cui l'utente è abilitato ad accedere durante la sua sessione di lavoro.

Il Single Sign-On

La definizione di Single Sign-On (SSO) come abbiamo visto è molto semplice ma non è altrettanto facile la sua realizzazione pratica in quanto bisogna utilizzare un meccanismo che ci permetta di esternalizzare tutte le informazioni necessarie all'autenticazione di un utente su un "dispositivo" condiviso da tutte le risorse coinvolte del processo di SSO.

Di conseguenza al vantaggio funzionale di consentire a un utente di autenticarsi solo una volta e al vantaggio gestionale di avere un unico punto centralizzato di amministrazione delle informazioni necessarie all'autenticazione si aggiunge lo svantaggio tecnico che tutte le risorse coinvolte nel processo di SSO devono supportare il medesimo gestore di sicurezza.

Tra le tecnologie standard più diffuse che permettono di implementare un gestore di sicurezza per SSO meritano di essere citati i server Lightweight Directory Access Protocol (LDAP) [1][4], il Simple Authentication and Security Layer (SASL) [2][4] e i server Kerberos [3][4].

Il Single Sign-On e il Business-to-Business

Una interessante applicazione del SSO è il Business-to-Business (B2B).

Per B2B si intendono tutte quelle iniziative tese ad integrare le applicazioni di varie aziende in un unico "prodotto" da offrire agli utilizzatori finali.

Un classico scenario di B2B potrebbe richiedere la cooperazione di applicazioni internet eterogenee, sviluppate su ambienti differenti, in cui è fondamentale evitare che il cliente finale debba autenticarsi più volte ma è impossibile appogiarsi su un unico gestore della sicurezza.

Se l'applicazione da integrare espone i suoi servizi come Web Services la soluzione è semplice e consiste nel chiamare direttamente i Web Services gestendo la sicurezza a livello di trasporto (HTTPS) o di messaggio (WS-Security).

Ma se l'integrazione avviene a livello di pagina HTML il problema diventa più complesso e la sua soluzione potrebbe consistere nell'adottare un protocollo di SSO applicativo basato sullo scambio di un token di sicurezza basato sulla firma digitale.

Introduzione alla firma digitale

La firma digitale è un meccanismo che garantisce l'identità del mittente e l'integrità del messaggio che il mittente ha prodotto per i suoi destinatari. L'identità del mittente, individuo o organizzazione, avviene per mezzo di una coppia di chiavi asimmetriche, ovvero della chiave privata e della corrispondente chiave pubblica usata sotto forma di certificato personale. Se un mittente codifica dei dati con la sua chiave privata solo i destinatari in possesso della corrispondente chiave pubblica, o del suo certificato, possono decodificarli. L'integrità dei dati invece si base sull'utilizzo dei digest di messaggio. Un digest di messaggio è una sorta di impronta digitale che rappresenta in pochi bytes un qualsiasi flusso di dati, in pratica ogni flusso di dati deve generare una impronta differente.

Figura 1 - Firma digitale: generazione
Figura 1 - Firma digitale: generazione

Il mittente per firmare un messaggio deve calcolare il suo digest e codificarlo con la sua chiave privata, infine deve spedire al destinatario sia il messaggio che la sua firma digitale. Il destinatario per verificare l'identità del mittente e l'integrità del messaggio deve fornire al motore di verifica il messaggio che ha ricevuto, la firma digitale e la chiave pubblica del mittente. La firma risulterà valida solo se il messaggio, la firma digitale e la chiave pubblica sono autentiche e combaciano tra di loro.

Figura 2 - Firma digitale: verifica
Figura 2 - Firma digitale: verifica

Le API di firma digitale fanno parte di J2SE [4]; il package java.security contiene le API di firma digitale mentre il package java.security.cert contiene le API per la gestione dei certificati. Il provider di sicurezza standard di Sun supporta i seguenti algoritmi di firma digitale:

  • MD2withRSA: digest MD2 (128bit) codificato con chiavi asimmetriche RSA
  • MD5withRSA: digest MD5 (128bit) codificato con chiavi asimmetriche RSA
  • SHA1withRSA: digest SHA1 (160bit) codificato con chiavi asimmetriche RSA
  • SHA1withDSA: digest SHA1 (160bit) codificato con chiavi asimmetriche DSA

Il Single Sign-On basato su un token di sicurezza

Il SSO basato su un token di sicurezza permette l'iteroperabilità tra applicazioni eterogenee in uno scenario di B2B. Supponiamo ad esempio di dover integrare due applicazioni internet a livello di pagina HTML, la prima applicazione (client) gestisce l'autenticazione e l'autorizzazione dell'utente e prepara l'URL per ridirigere il browser dell'utente all'entry point della seconda applicazione (server). Il client deve generare un URL che contiene l'ID univoco dell'applicazione richiedente, l'ID univoco della richiesta, i parametri applicativi e il codice di sicurezza basato sulla firma digitale della stringa con tutti parametri passati al server. Il server per controllare l'identità del mittente e l'integrità della richiesta deve verificare la firma digitale usando la chiave pubblica del client quindi se la richiesta risulta valida può permettere l'accesso al sito.

Un esempio J2EE

Vediamo ora come implementare un esempio di SSO applicativo basato sulle servlet Java.

La servlet SSOClientServlet (applicazione client)

Imposta l'URL del sito di destinazione; per una maggiore sicurezza una applicazione reale dovrebbe usare una connessione HTTPS:

StringBuffer url = new StringBuffer("http://localhost:8080/test/SSO/SSOServerServlet");
StringBuffer data = new StringBuffer();

Imposta l'ID univoco dell'applicazione client così com'è richiesto dall'applicazione server:

data.append("ID=").append("MYID").append('&');

Imposta tutti i parametri necessari all'applicazione server:

data.append("PARAMETER=").append("MYPARAMETER").append('&');

Imposta l'ID univoco della richiesta come una stringa con il formato yyyyMMddHHmmssnnniii dove yyyyMMdd è la data della richiesta, HHmmss è l'ora della richiesta, nnn è un numero progressivo da 1 a 999 e iii è l'ultimo byte dell'indirizzo IP del server che ha generato la richiesta:

data.append("REQUESTID=").append(getRequestId());

Genera la firma digitale di tutti i parametri della richiesta usando la chiave privata privateKey che ha già caricato nel metodo init della servlet:

Signature signature = Signature.getInstance("SHA1withRSA");
signature.initSign(privateKey);
signature.update(data.toString().getBytes());
byte[] tokenBytes = signature.sign();

Converte i bytes che rappresentano la firma digitale in una stringa BASE64; la classe Base64.java è inclusa negli esempi dell'articolo:

String token = new String(Base64.encode(tokenBytes));

Genera la stringa che rappresenta l'URL della richiesta concatenando tutti i parametri con la corrispondente firma digitale in formato BASE64:

url.append('?').append(data).append('&').append("TOKEN=").
  append(URLEncoder.encode(token, "8859_1"));

Infine ridirige il browser dell'utente al sito di destinazione:

httpServletResponse.sendRedirect(url.toString());

La servlet SSOServerServlet (applicazione server)

Recupera l'ID univoco del richiedente; una applicazione reale deve contrallare se l'ID è valido:

String id = httpServletRequest.getParameter("ID");

Recupera l'ID univoco della richiesta; una applicazione reale deve contrallare se l'ID è valido, se ha già ricevuto altre richieste con lo stesso ID e se la data e l'ora (yyyyMMddHHmmss) della richiesta è compreso in un intervallo di tempo accettato, ad esempio entro 5 minuti:

String requestId = httpServletRequest.getParameter("REQUESTID");

Recupera gli eventuali parametri applicativi:

String parameter = httpServletRequest.getParameter("PARAMETER");

Recupera il token di sicurezza in formato BASE64:

String token = httpServletRequest.getParameter("TOKEN");

Per verificare la firma digitale ricompone la stringa con tutti i parametri della richiesta, così come ha fatto l'applicazione client:

StringBuffer data = new StringBuffer();
data.append("ID=").append(id).append('&');
data.append("PARAMETER=").append(parameter).append('&');
data.append("REQUESTID=").append(requestId);

E converte la firma digitale da BASE64 in un array di bytes:

byte tokenBytes[] = Base64.decode(token.getBytes());

Infine verifica la firma digitale usando la chiave pubblica publicKey che ha già caricato nel metodo init della servlet:

Signature signature = Signature.getInstance("SHA1withRSA");
signature.initVerify(publicKey);
signature.update(data.toString().getBytes());
boolean ok = signature.verify(tokenBytes);

L'autenticazione è valida se tutti i parametri sono corretti e se la verifica della firma digitale ha avuto esito positivo; in questo caso il chiamante può accedere al sito e proseguire il suo lavoro, altrimenti bisogna generare una risposta di errore.

Conclusioni

Il SSO è una funzionalità molto importate non solo per le applicazioni di B2B che, come abbiamo visto, possono utilizzare una implementazione custom ma soprattutto in ambito intranet per la realizzazione dei cosidetti portali aziendali. Le applicazioni intranet infatti rappresentano l’ambiente più adatto alla realizzazione di una soluzione di SSO standard basata sulla tecnologia LDAP con SASL o Kerberos, argomenti da trattare magari in un prossimo articolo.

Bibliografia

  • [1] LDAP - http://www.ietf.org/rfc/rfc2251.txt
  • [2] SASL - http://www.ietf.org/rfc/rfc2222.txt
  • [3] Kerberos - http://web.mit.edu/kerberos/www/
  • [4] Java Security - http://java.sun.com/j2se/1.4.2/docs/guide/security/index.html

Risorse

Scarica l'esempio J2EE