Forms Authentication: un'applicazione multi login

di Cristian Civera, in ASP.NET,

La maggior parte delle applicazioni web dispongono anche di una sezione amministrativa e spesso ci troviamo di fronte a dei limiti dell'autenticazione Forms offerta dal .NET Framework. Non permette infatti di distiguere l'utente loggato "normale" (per svolgere le attività offerte dal sito) da uno con funzionalità amministrative.

Una soluzione è stata data in questo articolo , ma vi è ancora un problema: se vogliamo diversificare la pagina di login a seconda che si voglia accedere ad una sezione amministrativa o di una normale, come si fa?

Vi sono più modi per aggirare il problema:

  • creare una nuova applicazione web solo per la parte amministrativa. In questo modo possiamo usare la sezione forms del web.config e usare l'attributo loginUrl.
  • dare un accesso ristretto solo per utenti autenticati e in base al ruolo effettuare un redirect alla pagina di login corretta.

La prima soluzione ci costringe a duplicare un po' di codice e logica di configurazione. La seconda ci costringe a mettere in ogni pagina il codice necessario per validare il ruolo ed effettuare il redirect alla pagina di login. Personalmente non mi piace, sia perché difficile da mantenere, sia perché non sfruttiamo quella parte di ASP.NET che effettua i controlli sulle pagine e ne effettua il redirect alla pagina di login.

Allora cosa possiamo fare? Cerchiamo di capirci qualcosa.

L'architettura di ASP.NET

Le funzionalità messe a disposizione da ASP.NET sono implementate da moduli: classi che implementano l'interfaccia IHttpModule e che intercettano ogni fase dell'elaborazione di una richiesta.

Se date uno sguardo al file machine.config che si trova nella directory C:\WINDOWS\Microsoft.NET\Framework\[versione]\CONFIG\ andando a cercare la sezione <httpModules> del gruppo <system.web>, troverete (di default):

  <add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
  <add name="Session" type="System.Web.SessionState.SessionStateModule" />
  <add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
  <add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
  <add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
  <add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
  <add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />

Dai nomi avrete già intuito com'è l'architettura di ASP.NET. In particolare l'autenticazione Forms è gestita dal modulo FormsAuthenticationModule che si incarica di prelevare il nostro ticket di autenticazione dal cookie e creare il Principal (che identifica un utente, anonimo e non, nell'arco della richiesta) raggiungibile dalle nostre pagine attaverso la proprietà HttpContext.Current .User .

Scendendo ancor di più nel dettaglio, il modulo intercetta l'evento AuthenticateRequest di HttpApplication, nel quale preleva il cookie in base alle informazioni della sezione form, guarda se il cookie è ancora valido, se questo non è persistente lo "rivitalizza" spostando ad oltranza la sua scadenza, crea un nuovo GenericPrincipal passando come identity un FormsIdentity e nessun ruolo, lo associa a HttpContext.Current.User e riscrive il cookie in base alla configurazione di encryption dei dati.

A questo punto interviene UrlAuthorizationModule che intercetta l'evento AuthorizeRequest (scatenato subito dopo AuthenticateRequest ), che in base all'attuale Principal convalida o meno l'accesso alla pagina richiesta guardando la sezione di configurazione authorization nel machine.config, web.config della root, web.config delle sottodirectory ed eventuali sezioni location (in ordine di priorità).

Nel caso in cui l'utente non abbia accesso, valoriza la proprietà StatusCode di HttpContext.Current a 401 (Unauthorized) e richiama il metodo CompleteRequest di HttpApplication. In questo modo la pagina non viene processata dall'handler, ma continua con i successivi passaggi.

In questo lungo cammino interviene di nuovo FormsAuthenticationModule che intercetta l'evento EndRequest e controlla lo StatusCode. Se è 401, come si può già immaginare, fa il redirect alla pagina di login.

Ed ora la parte pratica

Questa spiegazione seppur lunga era fondamentale per capire ciò che dobbiamo fare. Creeremo anche noi un modulo nella quale intercetteremo l'evento EndRequest, valutando lo StatusCode e la pagina richiesta. In base a queste informazioni effettueremo il redirect alla pagina di login corretta.

La cosa bella è che se tutto funziona, potremo sfruttare questa funzionalità in tutte le nostre future applicazioni web, aggiungendo un paio di righe nel web.config.

Già che ci siamo, facciamo ancora le cose più elegantemente. Prepariamo una nostra sezione di configurazione (un esempio si trova qui ) che ci darà la facoltà di specificare nel web.config le pagine login:

<multiLogin xmlns="http://schemas.aspitalia.com/MultiLogin">
  <page path="users.aspx" loginUrl="loginUsers.aspx" />
  <page path="admin.aspx" loginUrl="loginAdmin.aspx" />
  ...
</multiLogin>

In base alla pagina richiesta viene specificato la pagina di login. Nell'allegato trovate la classe per la sezione e lo schema xsd.

Non mi soffermo sulla sezione di configurazione, cosa abbastanza semplice, perciò passiamo subito alla classe modulo.

Come già accennato dobbiamo implementare l'interfaccia System.Web.IHttpModule di cui membri sono due metodi: Init e Dispose.

Il primo viene richiamato dal motore ASP.NET all'avvio dell'applicazione web, il secondo quando viene arrestata.

Intercettiamo quindi in fase di Init l'evento EndRequest:

public class MultiLoginModule : IHttpModule
{
   private static MultiLoginConfig_settings = null;

   public MultiLoginModule()
   {
   }

   public static MultiLoginConfig Settings
   {
      get
      {
        if (_settings == null)
        {
           //Carico la config
           _settings = MultiLoginConfig)ConfigurationSettings.GetConfig("multiLogin");
        }
        return _settings;
      }
   }
   
      public void Init(HttpApplication context)
   {
      context.EndRequest += new EventHandler(context_EndRequest);     
   }
   public void Dispose()
   {
   }
2 pagine in totale: 1 2

Attenzione: Questo articolo contiene un allegato.

Contenuti dell'articolo

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti