martes, 17 de febrero de 2015

Claims Series 4: Transformaciones

Después de bastante tiempo continuo con esta serie dedicada a la seguridad basada en claims.

Autenticación y Tokens


Una de los principales "mantras" que debemos asumir es que las aplicaciones no deben preocuparse de como los claims han sido generados, por ejemplo que tipo de autenticación se ha utilizado para determinar que el username es "Tancredo" (existe un claim que dice eso).

Tenemos en .NET 4.5 un mecanismo basado en plugins que nos aísla de cada tipo de autenticación (de su implementación concreta) y que se encarga de generar los claims necesarios. Los mecanismo de autenticación soportados son varios: Autenticación tipo Forms, Windows integrada, SAML, etc... 

Estos mecanismo emiten tokens de seguridad en diferentes formatos, de los cuales podemos extraer los claims utilizando un objeto de tipo SecurityTokenHandler, cuyas funciones son:

  • Leer tokens
  • Escribir tokens
  • Validar tokens
  • Determinar el tipo de un token
Para ello en el framework disponemos de clases especializadas para cada tipo de credenciales:

  • KerberosSecurityTokenHandler
  • RsaSecurityTokenHandler
  • SamlSecurityTokenHandler
  • UserNameSecurityTokenHandler
  • Otras
 
El pipeline utilizado es el siguiente:
 
  1. La aplicación recibe un token en un formato dado (XML, binario, etc...)
  2. El token es deserializado (Método ReadToken)
  3. El token es validado (Método ValidateToken). El resultado de una validación afirmativa es la generación de un ClaimsPrincipal
  4. El objeto ClaimsPrincipal  es transformado de acuerdo a las necesidades (opcional)
La autenticación es algo que se escapa del ámbito de lo que estamos tratando, así que vamos a centrarnos en el punto 4. Una vez que el mecanismo de autenticación ha generado un ClaimsPrincipal, puede que no lleve la información que espero.  ¿Cómo lo transformo según necesite?


Transformando Claims


Para transformar, lo que debemos hacer es generar una nueva clase, que heredando de ClaimsAuthenticationManager sobrescriba el método Authenticate.

   1:      public class CustomClaimsTransformer : ClaimsAuthenticationManager
   2:      {
   3:          public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   4:          {
   5:              throw new NotImplementedException();
   6:          }
   7:      }


Antes de empezar hay que añadir las referencias pertinentes:
 
 
 
Pues bien, vamos a mi implementación de ejemplo. Lo que voy a hacer es analizar el username que recibo para ver si es un usuario local a un equipo concreto que tengo en casa (mi desktop). Si es así emitiré un claim indicándolo.
Asumimos que este claim será mas tarde utilizado por la aplicación (¿no es muy buen ejemplo verdad?)

   1:   public override ClaimsPrincipal Authenticate(string resourceName, ClaimsPrincipal incomingPrincipal)
   2:          {
   3:              string nameClaimValue = incomingPrincipal.Identity.Name;
   4:              List<Claim> claimsCollection = new List<Claim>();
   5:              claimsCollection.Add(new Claim(ClaimTypes.Name, nameClaimValue));
   6:                          
   7:              if(nameClaimValue.IndexOf("RAISTLIN-DT") >=0)
   8:              {
   9:                  //We add claim indicating user is working on my desktop
  10:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  11:   
  12:              }
  13:              else
  14:              {
  15:                  //We add claim indicating user is not working on my desktop
  16:                  claimsCollection.Add(new Claim("http://serginet.com/isUserUsingMyDesktop", "true"));
  17:              }
  18:   
  19:              return new ClaimsPrincipal(new ClaimsIdentity(claimsCollection, "MyCustomAuth"));
  20:          }       

El siguiente paso es configurar la aplicación para que se utilice el nuevo manager:

   1:  <?xml version="1.0" encoding="utf-8" ?>
   2:  <configuration>
   3:    <configSections>
   4:      <section name="system.identityModel" 
   5:          type="System.IdentityModel.Configuration.SystemIdentityModelSection, System.IdentityModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
   6:    </configSections>
   7:     
   8:  <system.identityModel>
   9:    <identityConfiguration>
  10:      <claimsAuthenticationManager type="Claims4_Transformaciones.CustomClaimsTransformer,Claims4_Transformaciones"/>
  11:    </identityConfiguration>
  12:  </system.identityModel>
  13:    
  14:   <startup> 
  15:          <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  16:      </startup>
  17:   
  18:  </configuration>

Y para acabar, podemos hacer uso del nuevo manager:

   1:   class Program
   2:      {
   3:          static void Main(string[] args)
   4:          {
   5:              SetCurrentPrincipal();
   6:          }
   7:   
   8:          private static void SetCurrentPrincipal()
   9:          {
  10:              WindowsPrincipal incomingPrincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
  11:              Thread.CurrentPrincipal = FederatedAuthentication.FederationConfiguration.IdentityConfiguration
  12:                  .ClaimsAuthenticationManager.Authenticate("none", incomingPrincipal);
  13:          }
  14:   
  15:      }

Si depuramos veremos que obtenemos un ClaimsPrincipal que contiene los 2 claims que hemos emitido:
 
 
 
Espero que os haya gustado!  En el próximo post "cerraremos el circulo" para explicar como permitir que se ejecuten acciones o no en una aplicación (autorización) en base a los claims del usuario.
 
Como siempre os dejo el código

Posts publicados en Claims Series:

No hay comentarios:

Publicar un comentario