Single Sign-On con le Membership API di ASP.NET 2.0
Introduzione
La problematica del Single Sign On (SSO) consiste nella possibilità di autenticarsi una volta sola, fornendo le credenziali adeguate, e di essere riconosciuti da più applicazioni all'interno dello stesso "dominio fiduciario". L'esempio più conosciuto di SSO è costituito da Active directory e dai domini di windows, i quali ci consentono di autenticarci da una macchina appartenente ad un dominio e di utilizzare tutte le risorse appartenenti a tale dominio per le quali ci è stato assegnato l'accesso. L'autenticazione basata su domini Active Directory (AD) è estremamente utile nel caso in cui ci si trovi ad operare in ambienti intranet, ma diventa problematica si debba lavorare con applicazioni esposte su internet, le quali alle volte prevedono anche dei meccanismi di auto-registrazione. Pensiamo al malcapitato sistemista che si trova utenti creati senza controllo all'interno della struttura di Active Directory.
E' quindi interessante cercare di trovare una soluzione al problema di far partecipare più applicazioni web ad una sorta di "dominio fiduciario", in modo che una volta autenticato su una di queste applicazioni, il "token" di sicurezza venga condiviso tra tutte, e con esso l'identità dell'utente. Pensiamo a questo punto ad una possibile architettura che comprenda un Blog, un Portale, un Forum un'applicazione per le vendite ed una per la gestione dei reports. La problematica è sicuramente sentita, e certamente molti di noi hanno cercato in passato di risolverla nei modi più disparati.
Membership e Role API, al salvataggio!
L'infrastruttura Membership di ASP.NET 2.0, o meglio del FX 2.0, permette di utilizzare uno storage di utenti strutturato ed offre una API per la creazione, cancellazione, modifica degli utenti serializzati in questo database. Essa offre però una cosa in più rispetto all'impiego di un metodo di serializzazione custom dei nostri utenti, che tutti almeno una volta, abbiamo realizzato: la standardizzazione.
E' indubbio infatti, che qualunque applicazione utilizzi Membership API per memorizzare gli utenti, offre un punto di contatto per la condivisione delle credenziali utente tra applicazioni web, anche sviluppate da team differenti. In Figura 1 possiamo vedere l'infrastruttura architetturale della soluzione di SSO.
L'eleganza della Membership API, quindi, consente di superare ogni problema, raggiungendo l'obiettivo di condividere le credenziali dell'utente attraverso più applicazioni web. In questo modo sarà possibile, ad esempio, creare un'applicazione web dedicata alle vendite e, successivamente, un'applicazione web dedicata alla reportistica. Queste due applicazioni potranno condividere le informazioni relative agli utenti ed ai loro ruoli, in modo da uniformare la gestione della sicurezza.
Ora, come implementare una feature così appetibile? Beh, vi assicuro che è più facile farlo che spiegarlo.
Implementazione
Per semplicità, considereremo il caso in cui tutte le applicazioni web risiedano sullo stesso server. Come prima cosa, dobbiamo evitare di usare il "classico" database SQL Express che il buon vecchio VS2005 crea automaticamente per noi. Piuttosto, creiamo un database che fungerà da repository condiviso di credenziali (Shared Credential Repository, SCR), mediante il tool aspnet_regsql.exe, che possiamo trovare nella directory %WINDOWS%\Microsoft.NET\Framework\v2.0.50727. Lasciando come <default> la selezione del database, creeremo un DB di nome ASPNETDB nel nostro server SQL che in seguito utilizzeremo per i nostri fini. A questo punto, visto che siamo nei paraggi, diamo uno sguardo al file machine.config che si trova nella sottodirectory CONFIG. Al suo interno possiamo trovare le seguenti parti "interessanti":
<connectionStrings>
<!-- stringa di connessione troncata per esigenze editoriali -->
<add name="LocalSqlServer"
connectionString="data source=.\SQLEXPRESS;
Integrated Security=SSPI;
AttachDBFilename=|DataDirectory|aspnetdb.mdf;
User Instance=true"
providerName="System.Data.SqlClient" />
</connectionStrings>
<membership>
<providers>
<add name="AspNetSqlMembershipProvider"
type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
connectionStringName="LocalSqlServer"
enablePasswordRetrieval="false"
enablePasswordReset="true"
requiresQuestionAndAnswer="true"
applicationName="/"
requiresUniqueEmail="false"
passwordFormat="Hashed"
maxInvalidPasswordAttempts="5"
minRequiredPasswordLength="7"
minRequiredNonalphanumericCharacters="1"
passwordAttemptWindow="10"
passwordStrengthRegularExpression="" />
</providers>
</membership>
Il punto su cui ragionare, in vista delle nostre applicazioni, è la ConnectionString "LocalSqlServer" e come questa viene utilizzata dal provider AspNetSqlMembershipProvider. Il nostro obbiettivo è di mantenere questo provider senza apportare modifiche in ciascuna applicazione web, in modo che modificando il comportamento della membership all'interno del Machine.config, tutte le applicazioni installate sul nostro server ne beneficino. Del resto è anche una questione di coerenza nelle policies di sicurezza. Preso atto di questo, creiamo una applicazione web di nome SalesApp, aggiungiamo un paio di pagine Login.aspx e Register.aspx nelle quali inseriamo, rispettivamente, un controllo Login ed un controllo User Creation Wizard. Poi nella pagina Default.aspx aggiungiamo un controllo LoginStatus e un controllo LoginName. Il pezzo forte di tutto questo, comunque è il file Web.config, che deve essere così composto:
<?xml version="1.0"?>
<configuration>
<appSettings/>
<connectionStrings>
<clear/>
<!-- stringa di connessione troncata per esigenze editoriali -->
<!--configure Shared Credential Repository-->
<add
name="LocalSqlServer"
connectionString="SERVER=MYSERVER;
DATABASE=ASPNETDB;UID=sa;PWD=password"
providerName="System.Data.SqlClient" />
</connectionStrings>
<system.web>
<compilation debug="false" strict="false" explicit="true" />
<authentication mode="Forms">
<!--configure authentication element: name, path, protection-->
<forms name=".SHAREDTOKEN" loginUrl="login.aspx" protection="All"
timeout="30"
path="/"
requireSSL="false"
slidingExpiration="true"
cookieless="UseCookies"/>
</authentication>
<!--configure machineKey. Must be the same for each application!!-->
<machineKey validationKey="95E209A1882F59B1585FD2691BB0AEF28E8158C1CD2
6EADA9B2B3CF8821F2D3D39347B487F72D94E438E4EE510A730FE979A3
E79D18A5DB453FE87979D25AACC"
decryptionKey="B753AE2B2CEAFD2BA6B938744ECD8550ECE5F6BB51
506E43"
validation="SHA1" />
<!--deny access to unauthenticated users-->
<authorization>
<deny users="?" />
</authorization>
</system.web>
</configuration>
Esaminiamo, dunque, questo file, fondamentale per il buon funzionamento della nostra "Shared-Membership"; la stringa di connessione LocalSqlServer è stata modificata, in modo da farla puntare allo Shared Credential Repository creato in precedenza. Nell'elemento authentication si può vedere l'attibuto mode, impostato a Forms, ed all'interno, l'elemento forms configurato con un name, un path ed una protection ben definite. Dobbiamo fare attenzione a questi valori, perchè dovranno essere uguali in ogni applicazione che vogliamo far entrare nella nostra Shared Membership. Oltre a questa impostazione, un altro elemento deve essere identico in tutte le nostre web application, ed è l'elemento machineKey. I tre attributi validationKey, decryptionKey e validation devono essere identici sempre per tutte le web application che partecipano alla SSO. L'emento machineKey, unitamente agli attributi validationKey, decryptionKey e validation, rappresenta le chiavi con le quali viene validato e cifrato il viewstate. le impostazioni di default sono "autogenerate" e "isolateApps", ovvero i valori vengono autogenerati e le applicazioni vengono mantenute isolate. Nel nostro caso, impostando un valore ben definito per tutte le applicazioni web partecipanti alla "web farm", abbiamo imposto dei valori da noi prescelti, permettendo così lo Sharing del token dell'autenticazione Forms. Per ulteriori approfondimenti consultare l'argomento ValidationKey. Per generare valori validi riferitevi a MachineKey Generator.
A questo punto, il gioco è fatto! Per creare il primo utente, rimuoviamo il deny users="?" in modo da poter navigare la pagina register.aspx e creare l'utente, oppure, in alternativa, è possiamo utilizzare l'ASP.NET Site Management Tool. L'utente verrà creato all'interno del repository condiviso, e sarà a disposizione di tutte le applicazioni che utilizzeranno questo pattern di autenticazione condivisa. Ripristinando il deny, l'effetto che possiamo osservare è che creando un'applicazione di nome ReportApp, e copiando il file web.config della SalesApp nella ReportApp, e infine creando un hyperlink che dalla prima punti alla seconda e vi autenticate nella SalesApp, una volta giunti alla ReportApp, questa vi "riconoscerà" e nella pagina di default comparirà il nome del vostro utente. Stiamo quindi lavorando con un utente in un'applicazione web, essendoci autenticati presso un'altra applicazione. Portando il ragionamento alle estreme conseguenze, potremmo creare un'applicazione per l'autenticazione che rinvia ad altre applicazioni concrete che implementino le funzionalità desiderate. A corredo dell'articolo potete trovare un semplice progetto con due applicazioni web per testare questa feature estremamente potente di ASP.NET 2.0.
Effetti collaterali
Gli effetti collaterali di questa implementazione sono estremamente positivi. E' infatti possibile invocare dalle nostre pagine il metodo User.IsInRole("<NomeRuolo>") e lavorare in modo completamente trasparente rispetto a quale applicazione ha effettuato l'autenticazione. In pratica abbiamo a disposizione in ogni momento informazioni riguardanti il nostro utente, e le possiamo utilizzare per effettuare controlli di sicurezza nelle nostre applicazioni. Inoltre abbiamo un solo repository che contiente tutti i nostri utenti ed i nostri ruoli, e questo significa sicuramente meno manutenzione per le persone che gestiscono gli accessi alle nostre applicazioni web, e la possibilità di realizzare un'applicazione ad hoc per la gestione di utenti e ruoli, valida per ogni altra applicazione del nostro "reame". Ultimo, ma non meno importante, abbiamo l'effetto positivo di non caricare i nostri utenti di un numero cospicuo di passwords da ricordare. Questo è uno dei problemi più sentiti a livello utente, in quanto l'utente medio vorrebbe ricordare una sola login ed una sola password ed essere riconosciuto in tutte le applicazioni.
Conclusioni
Questo articolo ha sondato la possibilità di condividere le credenziali utente, ovvero identità e ruoli, attraverso varie applicazioni web. Ricordiamo comunque che per mettere in sicurezza una soluzione di questo tipo, potrebbe essere una buona idea far accedere tramite protocollo https alla form che effettua il login, in modo tale da non far passare in chiaro le credenziali utente la prima volta che vengono fornite dall'utente. Nel caso in cui vogliate riportare le impostazioni dei vari provider nel Web.Config, fate attenzione al nome dell'applicazione. Se volete condividere Membership Ruoli e Profili tra le applicazioni, dovete impostare l'attributo name allo stesso valore per ogni applicazione!
-
Ho letto l'articolo.
Cercavo risposte alla mancata connessione al database "ASPNETDB.MDF" che ho nella cartella
App_Data.
Sto' cercando di conoscere "Visual Web Developer Express edition", e alla fine si e' presentata
la necessita' di usare la connessione ad un database.
Ho copiato la parte di codice da inserire nel Web.config.
Provero' a incollare , sperando che sblocchi la connessione che attualmente e' impossibile.
Grazie comunque per l'articolo a disposizione di tutti, anche dei meno preparati.
di
luca
-
21/10/2007 6.25.08
indietro