¿Le resultó útil esta página?
Sus comentarios sobre este contenido son muy importantes. Háganos saber su opinión.
¿Tiene comentarios adicionales?
Caracteres restantes: 1500
Uso de la seguridad basada en funciones con Web Services Enhancements 2.0
Collapse the table of content
Expand the table of content

Uso de la seguridad basada en funciones con Web Services Enhancements 2.0

18 de Julio de 2005

Publicado: Enero de 2005

Ingo Rammer

thinktecture

Este artículo se aplica a:

Microsoft® .NET Framework

Web Services Enhancements 2.0 SP1 para Microsoft® .NET

Especificación de WS-Policy

Resumen: en este artículo se describe cómo utilizar Web Services Enhancements 2.0 SP1 (WSE 2.0) para Microsoft .NET en la tarea de integrar la autenticación de WS-Security basada en X.509 con características de la seguridad basada en funciones en Microsoft .NET Framework. Asimismo, destaca el uso de WS-Policy en WSE 2.0 para simplificar enormemente las tareas. (26 páginas impresas.)

Descargue el código de ejemplo asociado, wseRoleBasedSecurity_Samples.msi code.

En esta página

Introducción Introducción
Un método abreviado para crear mensajes firmados Un método abreviado para crear mensajes firmados
Uso de la seguridad declarativa e imperativa basada en funciones Uso de la seguridad declarativa e imperativa basada en funciones
Identidades y principales Identidades y principales
Montaje final Montaje final
Una única línea de código Una única línea de código

Introducción

Microsoft .NET Framework y Microsoft ASP.NET admiten una cierta cantidad de características de seguridad que se puede agregar al código. ¿No sería fantástico poder utilizar una construcción similar a HttpContext.Current.User.IsInRole() para proteger el acceso a los métodos de servicios Web basados en WSE? En este artículo, enseñaré a combinar la capacidad que tiene WSE 2.0 de firmar y autenticar mensajes con el sistema de permisos basado en funciones que hay en .NET Framework.

En las aplicaciones o servicios Web convencionales, se puede confiar simplemente en los medios que ofrecen la autenticación y cifrado disponibles en IIS (SSL). En este caso, se puede configurar un directorio de manera que requiera que el usuario envíe credenciales de inicio de sesión a través del protocolo HTTP utilizando la seguridad básica de HTTP o la seguridad integrada de Windows.

La utilización de HTTP para autenticar las solicitudes de los servicios Web podría parecer una buena idea al principio, pero en cuanto WS-Routing entra en el juego, la situación cambia sustancialmente: ya no existe una conexión HTTP directa entre el remitente y el destinatario final del mensaje, sino que hay un número potencialmente grande de protocolos diferentes que se podrían utilizar junto con la ruta de acceso de enrutamiento. Esto transforma todos los medios de seguridad establecidos a nivel de transporte en complementos puramente opcionales que no pueden garantizar la seguridad e integridad de los mensajes de un extremo a otro.

Una de las formas de proporcionar estos servicios de un extremo a otro para servicios Web en general consiste en firmar un mensaje saliente con un certificado X.509 de acuerdo con la especificación WS-Security.

Un método abreviado para crear mensajes firmados

Para obtener un certificado X.509, se puede utilizar una de las entidades emisoras de certificados (CA) conocidas como Verisign o simplemente crear una CA propia mediante los Servicios de Certificate Server de Windows. Después de la instalación de este servicio (que es un componente opcional de Windows 2000 Server o Windows Server 2003), se debe dirigir el explorador hacia http://<nombreservidor>/certsrv para solicitar la creación de un nuevo certificado. En cuanto un administrador del sistema autorice la solicitud, será posible agregar el certificado que se acaba de crear al almacén de certificados privado utilizando la misma aplicación Web disponible en http://<nombreservidor>/certsrv. Es necesario asegurarse de que se agrega el certificado raíz de la entidad emisora de certificados al almacén "Equipo local" del servidor Web seleccionando manualmente la ubicación del almacén (incluida la "ubicación física") durante la importación del certificado.

En cuanto se haya creado y obtenido el certificado X.509, ya se puede utilizar para firmar las solicitudes de servicios Web. Pero antes es necesario activar el proyecto para que admita Web Services Enhancements 2.0. Después de instalar WSE 2.0 en modo .NET Developer , se puede hacer clic con el botón secundario del mouse (ratón) en cualquier proyecto de Visual Studio® .NET y seleccionar WSE Settings 2.0 para abrir los cuadros de diálogo que se muestran en la figura 1.

Figura 1. Activación de Web Services Enhancements 2.0

En cuanto se seleccione esta casilla de verificación, sucederán una serie de cosas. Primero, se agregará de forma automática al proyecto una referencia a Microsoft.Web.Services.dll. Segundo, e incluso más importante, el comportamiento de los comandos Agregar referencia Web... y Actualizar referencia Web se modificará de tal manera que permitirá tener acceso más adelante a las propiedades de contexto adicional. Esto permite, por ejemplo, modificar los tokens de seguridad. Este cambio se lleva a cabo creando un segundo proxy para cada una de las referencias Web. Al nombre del proxy recién generado se le agregará el sufijo "Wse" (por ejemplo, en lugar de "MiServicio", el proxy se llamaría "MiServicioWse") y Framework seleccionará una clase base distinta para él: en lugar de heredar de System.Web.Services.Protocols.SoapHttpClientProtocol como hacen los proxies que no son WSE, los proxies WSE ampliarán Microsoft.Web.Services.WebServicesClientProtocol que contiene un número de propiedades adicionales.

Con esta nueva clase base, ya se tiene acceso a SoapContext de WSE y se puede agregar tokens y firmas de WS-Security a los mensajes salientes. Para demostrar en detalle la funcionalidad de las extensiones de seguridad basadas en funciones, he creado el servicio de demostración que se muestra a continuación:

using System;
using System.Web.Services;
using System.Security.Permissions;

[WebService(Namespace="http://schemas.ingorammer.com/wse/role-based-security-extensions/2003-08-10")]
public class DemoService: System.Web.Services.WebService
{
   [WebMethod]
   public string HelloWorld()
   {
      return "Hello World";
   }
}

A continuación, he agregado una referencia Web al proyecto, he especificado su Comportamiento de dirección URL como "dinámico" y he creado el siguiente archivo app.config para especificar la ubicación del servidor y el certificado que debe utilizar el cliente. Si continúa con este ejemplo en el equipo, recuerde que debe especificar el nombre de un certificado contenido en el almacén de certificados privado.

<configuration>
   <appSettings>
      <add key="RoleBasedSecurityClient.demosvc.DemoService" value="http://server/WSEDemo/DemoService.asmx"/>
      <add key="CertificateName" value="user1@example.com"/>
   </appSettings>
</configuration>

Ya se puede utilizar Web Services Enhancements 2.0 para crear un token de seguridad binario basado en un certificado X.509. Este token se adjuntará al mensaje saliente y se utilizará para firmarlo criptográficamente. En el siguiente código del cliente, he agregado la funcionalidad para obtener acceso al almacén de certificados privado del usuario y buscar el certificado especificado en el archivo de configuración que se ha indicado anteriormente.

using System;
// ...
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.Tokens;
using Microsoft.Web.Services2.Security.X509;
// ...

public static void Main(string[] args)
{
   String sto = X509CertificateStore.MyStore;

   // Open the certificate store 
   X509CertificateStore store = X509CertificateStore.CurrentUserStore(sto);
   store.OpenRead();

   // Find the certificate you want to use
   String certname = System.Configuration.ConfigurationSettings.AppSettings["CertificateName"];
   X509CertificateCollection certcoll = store.FindCertificateBySubjectString(certname);

   if (certcoll.Count == 0)
   {
      Console.WriteLine("Certificate not found");
   }
   else
   {
      X509Certificate cert =  certcoll[0];

      DemoServiceWse svc = new DemoServiceWse();
      SoapContext ctx = svc.RequestSoapContext;

      // Use the certificate to sign the message
      SecurityToken tok = new X509SecurityToken(cert);
      ctx.Security.Tokens.Add(tok);
      ctx.Security.Elements.Add(new MessageSignature(tok));

      // Invoke the web service
      String res = svc.HelloWorld();
   }

   Console.WriteLine("Done");
   Console.ReadLine();
}

Y nada más. Hemos llegado al final del método abreviado para los mensajes firmados. Si desea recorrer el camino largo con unas fantásticas vistas durante todo el recorrido, consulte el artículo de Matt Powell en WS-Security Authentication and Digital Signatures with Web Services Enhancements (en inglés) para obtener más información.

Alternativa: Directiva del cliente

En lugar de utilizar el proceso manual de creación de tokens y firmado de mensaje tal y como se ha ilustrado, también se puede utilizar sencillamente una directiva del cliente para adjuntar un certificado X.509 a cada solicitud saliente.

Para ello, abra las propiedades de WSE 2.0 del proyecto cliente y desplácese hasta la ficha Policy. Seleccione Enable Policy y haga clic en Add para agregar una directiva para el extremo predeterminado. En el siguiente cuadro de diálogo, seleccione Secure a client application y, a continuación, active Require signatures (es decir, borrar la opción Require encryption). Confirme la selección de X.509 Certificate como el tipo de token del cliente y haga clic en Select Certificate... para elegir un certificado que coincida.

Después de finalizar este asistente y cerrar el cuadro de diálogo de propiedades de WSE 2.0, verá que se ha agregado un archivo de caché de directivas al proyecto del cliente. Este archivo de directivas configura el marco de trabajo del cliente de WSE 2.0 de manera que incluirá de forma automática el certificado seleccionado en todos los mensajes salientes sin tener que escribir ni una sola línea de código.

Uso de la seguridad declarativa e imperativa basada en funciones

El mensaje está firmado. ¿Y ahora? Por una parte, se podría simplemente tener acceso al token de seguridad y a su certificado ubicados en el [WebMethod] del servidor y comprobar si coincide con el requerido para invocar el servicio. Aunque en principio este procedimiento pueda parecer práctico, se debe tener en cuenta que la aplicación deberá ser compatible con una serie de usuarios, cada uno de los cuales dispondrá de su propio certificado. Si la infraestructura del servicio Web es más compleja que el sencillo ejemplo que se ha mostrado anteriormente, seguramente también habrá diferentes funciones para los usuarios. Cada una de dichas funciones tendrá distintos permisos de forma que sólo los usuarios que pertenezcan a ciertas funciones puedan llamar a determinados métodos.

A la hora de implementar aplicaciones Web basadas en HTTP clásicas, podría simplemente obtener acceso a HttpContext para comprobar la pertenencia con los grupos de usuarios de Windows o los grupos de Active Directory tal y como se muestra a continuación:

[WebMethod]
public void AuthorizeOrder(long orderID)
{
  if (! HttpContext.Current.User.IsInRole(@"DOMAIN\Accounting"))
     throw new Exception("Only members of 'Accounting' may call this method.");

  // ... update the database
}

Esta sintaxis también permite comprobar conjuntos de permisos más minuciosos que, por ejemplo, dependan del valor de ciertos parámetros. En el siguiente ejemplo, los usuarios con la función "HR" podrían llamar al método, pero sólo los miembros con la función "PointyHairedBoss" podrían establecer un salario por encima de cierta cantidad:

public void SetMonthlySalary(long employeeID, double salary)
{
 if (! HttpContext.Current.User.IsInRole(@"DOMAIN\HR"))
     throw new Exception("Only members of 'HR' may call this method.");

  if (salary > 2000)
  {
    if (! HttpContext.Current.User.IsInRole(@"DOMAIN\PointyHairedBoss")) 
      throw new Exception("Only the pointy haired boss might set salaries larger than 2K");
  }

  // ... update the database

}

El código anterior se basa en una función ASP.NET estándar; si bien es una función que tiene un único pero grave inconveniente en este escenario: realmente no resuelve nuestro requisito de utilizar un esquema de autenticación de un extremo a otro ya que depende de la autenticación HTTP. Un segundo inconveniente es que la pertenencia del usuario a una función se determina buscando su pertenencia a algún grupo de Windows o Active Directory. Lo cual podría derivar en un número realmente grande de grupos de Windows si la aplicación necesita niveles de permisos minuciosos; situación que seguramente no sea del agrado de los administradores de sistemas.

Un administrador de tokens de seguridad personalizado acude al rescate

Afortunadamente, WSE 2.0 ofrece enlaces de extensibilidad necesarios que se pueden utilizar para la propia implementación de seguridad basada en funciones. Es más sencillo de lo que parece: sólo hay que proporcionar al tiempo de ejecución la información necesaria para determinar la pertenencia a una función de los usuarios. Después de todo, ¿de qué otra manera podría Framework saber que un mensaje firmado por "usuario1@ejemplo.com" se ha enviado por alguien que pertenece a la función PointyHairedBoss?

Para activar la seguridad basada en funciones en este entorno, se escribe un administrador de tokens de seguridad personalizado derivándolo de Microsoft.Web.Services.Security.Tokens.X509SecurityTokenManager. El nuevo administrador de tokens de seguridad, que presentaré más adelante, leerá un archivo de configuración como el que se muestra a continuación para determinar la asignación de certificados a las funciones:

<?xml version="1.0" encoding="utf-8" ?> 
<CertificateMapping>
  <Certificates>
    <!-- user1@example.com -->
    <CertificateMap Hash="f5 06 ba 1d 76 3b 59 1f ac 0c 3d ff e8 52 a3 41 44 b5 ed b1">
      <Roles>
        <Role>PointyHairedBoss</Role>
        <Role>Accounting</Role>
        <Role>HR</Role>
      </Roles> 
    </CertificateMap>
    <!-- user2@example.com -->
    <CertificateMap Hash="d7 fd 06 0d 43 7f 8f bb df a2 ee 9a 55 e4 c4 49 93 65 99 e4">
      <Roles>
        <Role>Accounting</Role>
        <Role>HR</Role>
      </Roles> 
    </CertificateMap>
  </Certificates>
</CertificateMapping>

En este archivo de configuración, debe existir una entrada <CertificateMap> para cada certificado para el que se desee asignar funciones. Un certificado se identifica mediante su código hash de huella digital que se puede obtener, por ejemplo, mediante la herramienta de administración de Servicios de Certificate Server de Windows disponible en la entidad emisora de certificados (CA) haciendo clic en Inicio>Herramientas administrativas>Entidad emisora de certificados. Al ejecutar esta herramienta y seleccionar un certificado que se ha emitido con anterioridad, aparecerá la ventana que se muestra en la figura 2 y que permite copiar el código hash al portapapeles.

También se puede obtener acceso a la misma información para todos los certificados ubicados en el almacén de certificados del usuario actual abriendo Internet Explorer y haciendo clic en Herramientas>Opciones de Internet>Contenido>Certificados.

Figura 2. Detalles del certificado

A continuación, se podrá utilizar este código hash para crear una nueva entrada <CertificateMap> en el archivo de configuración para especificar las funciones asociadas con el certificado dado. Posteriormente, el XmlSerializer leerá el archivo y lo deserializará en objetos basados en las dos clases siguientes:

public class CertificateMapping
{
   [XmlArrayItem(typeof(CertificateMap))]
   public ArrayList Certificates = new ArrayList();

   public CertificateMap this[String hash]
   {
      get
      {
         foreach (CertificateMap cert in Certificates)
         {
           if  (cert.CertificateHash.Replace(" ",").ToUpper() == hash.ToUpper()) 
             return cert;         
         }
         return null;
      }
   }
}

public class CertificateMap
{
   [XmlAttribute("Hash")]
   public String CertificateHash;

   [XmlArrayItem("Role", typeof(String))]
   public ArrayList Roles = new ArrayList();

Presentación de X509SecurityTokenManager

El administrador de tokens debe heredar de Microsoft.Web.Services.Security.Tokens.X509SecurityTokenManager. Después de registrar esta clase con Framework, cada token de seguridad binario X509 contenido en un mensaje de solicitud pasará a través de este nuevo método AuthenticateToken() de la clase antes de alcanzar el [WebMethod]. Por lo tanto, este método ofrece la posibilidad de crear un objeto IPrincipal coincidente para cada token, lo que finalmente permitirá enlazar de nuevo con el modelo de seguridad de .NET.

Para implementar este administrador de tokens de seguridad, primero se deben agregar referencias a los espacios de nombres necesarios:

using System;
using System.Collections;
using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2;
using Microsoft.Web.Services2.Security.Tokens;
using System.Security.Principal;
using System.Threading;
using System.Xml.Serialization;
using System.IO;
using System.Web;
using System.Security.Permissions;

A continuación, se puede omitir el método AuthenticateToken() para tener acceso al token de seguridad entrante y adjuntarle un IPrincipal coincidente.

namespace RoleBasedSecurityExtension
{
  [SecurityPermission(SecurityAction.Demand, 
     Flags=SecurityPermissionFlag.UnmanagedCode)]
   public class X509RoleBasedSecurityTokenManager: X509SecurityTokenManager
   {
    protected override void AuthenticateToken(X509SecurityToken token)
    {
      base.AuthenticateToken(token);
      token.Principal = new CertificatePrincipal(token.Certificate); 
    }
   }
}

Identidades y principales

Las características de seguridad basadas en funciones de .NET Framework están formadas por objetos Principal e Identity asignados a un contexto de solicitud. Un objeto principal contiene las funciones a las que pertenece el usuario actual, mientras que el objeto identity almacena información sobre el propio usuario y sobre cómo se ha autenticado al usuario.

Estas dos interfaces se pueden encontrar en System.Security.Principal y tienen el siguiente aspecto:

public interface IPrincipal 
{
    IIdentity Identity { get; }
    bool IsInRole(string role);
}

public interface IIdentity 
{
    string AuthenticationType { get; }
    bool IsAuthenticated { get; }
    string Name { get; }
}

Para completar las extensiones de seguridad basadas en funciones, he implementado un objeto principal personalizado (CertificatePrincipal) y un objeto identity personalizado (CertificateIdentity) que da acceso a la pertenencia a las funciones del usuario actual y al certificado utilizado para la autenticación.

El administrador de tokens de seguridad invoca al constructor público de la clase siempre que un mensaje entrante llega al servidor, y toma un objeto X509Certificate como parámetro. El constructor cargará la asignación de funciones desde el archivo XML si se ha modificado o utilizará la versión en caché que se ha almacenado en una variable estática. El filtro buscará entonces el certificado dado en la información cargada desde el archivo XML. Comprueba si la información de pertenencia a las funciones se ha grabado para el certificado dado. Si no es así, se generará una excepción, o se creará el correspondiente objeto identity.

Para implementar este objeto principal, primero se deben incluir los espacios de nombres necesarios:

using System;
using System.Xml.Serialization;
using System.IO;
using System.Security.Principal;
using Microsoft.Web.Services2.Security.X509;

Si es necesario, el constructor público lee el archivo XML y comprueba si se ha configurado el certificado del remitente en certmap.config. En cuyo caso creará un nuevo objeto CertificateIdentity y, en caso contrario, generará una excepción.

  public class CertificatePrincipal: IPrincipal
  {
    private static CertificateMapping _map;
    private static DateTime _certmapDateTime = DateTime.MinValue;
      
    private CertificateMap _certmap;
    private CertificateIdentity _ident;

    public CertificatePrincipal(X509Certificate cert)
    {
      String file = System.Web.HttpContext.Current.Server.MapPath("certmap.config");

      // compare the file's date with the cached version
      FileInfo f = new FileInfo(file);
      DateTime fileDate = f.LastWriteTime;

      // reload if necessary
      if (fileDate > _certmapDateTime)
      {
        XmlSerializer ser = new XmlSerializer(typeof (CertificateMapping));

        using (FileStream fs = new FileStream(file,FileMode.Open,FileAccess.Read))
        {
          _map = (CertificateMapping) ser.Deserialize(fs);
        }

        _certmapDateTime = fileDate;
      }

      _certmap = _map[cert.GetCertHashString()];
      if (_certmap == null)
      {
        throw new ApplicationException("The certificate " + 
          cert.GetCertHashString() + " has not been configured.");
      }
      _ident = new CertificateIdentity(cert);
    }

El resto de la implementación del objeto principal ofrece un medio de tener acceso a la pertenencia a funciones configurada en el archivo XML y una propiedad para recuperar el objeto Identity:

  public bool IsInRole(string role)
  {
    return _certmap.Roles.Contains(role);
  }

  public System.Security.Principal.IIdentity Identity
  {
    get
    {
      return _ident;
    }
  }
}

La clase CertificateIdentity contiene una implementación repetitiva de un objeto IIdentity con una propiedad adicional para tener acceso al objeto X509Certificate que se ha utilizado para firmar el mensaje.

public class CertificateIdentity: IIdentity 
{

  private X509Certificate _x509cert;

  internal CertificateIdentity(X509Certificate cert)
  {
    _x509cert = cert;
  }

  public bool IsAuthenticated
  {
    get
    {
      return true;
    }
  }

  public string Name
  {
    get
    {
      return _x509cert.GetName();
    }
  }

  public string AuthenticationType
  {
    get
    {
      return "X.509";
    }
  }

  public X509Certificate Certificate
  {
    get 
    {
      return _x509cert;
    }
  }
}

Montaje final

El último paso para completar la funcionalidad deseada consiste en registrar el administrador de tokens recién creado con la aplicación de servidor. Tras la instalación de Web Services Enhancements 2.0, existen dos formas distintas de llevar a cabo este proceso. Se puede editar directamente el archivo web.config o utilizar la herramienta de configuración de WSE 2.0 que he comentado en la figura 1.

Para utilizar la herramienta de configuración de WSE 2.0 en Visual Studio .NET, haga clic con el botón secundario del mouse en un proyecto de servicio Web y seleccione WSE Settings 2.0. Aparecerá el cuadro de diálogo que se muestra en la figura 3. Compruebe que las dos casillas de verificación están seleccionadas.

Figura 3. Activación de WSE y la canalización de procesamiento extendida para proyectos de servicios Web

A continuación, puede ir a la ficha Security (Seguridad) para especificar el nombre del administrador de tokens tal y como se muestra en la figura 4.

Figura 4. Agregar un nuevo administrador de tokens y comprobar la configuración de certificados.

Para agregar un nuevo administrador de tokens, haga clic en Add... y rellene los datos tal y como se muestra en la figura 5. Tenga en cuenta que la entrada Type se debe especificar en el formato

<espacio de nombres>.<nombre de clase>, <nombre de ensamblado>

que conforma el nombre completo del tipo del ejemplo: RoleBasedSecurityExtension.X509RoleBasedSecurityTokenManager, RoleBasedSecurityExtension. ValueType se debe establecer de acuerdo con la especificación de WS-Security. En este caso el valor correcto es http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3

Figura 5. Adición de un nuevo administrador de tokens

El paso final es configurar un archivo de directivas en la ficha Policy tal y como se muestra en la figura 6. Dentro de poco estudiaremos el contenido de este archivo de directivas.

Figura 6. Establecimiento del nombre del archivo de directivas

La combinación del administrador de tokens de seguridad, el archivo de directivas, el objeto principal personalizado y el objeto identity personalizado, permite utilizar la seguridad basada en funciones de una forma puramente descriptiva mediante el empleo de WS-Policy.

Para activar la comprobación automática de los mensajes entrantes y su certificado X.509, haga clic en Add en el cuadro de diálogo anterior para crear una directiva para el extremo predeterminado. En el siguiente cuadro de diálogo, debe seleccionar la opción Secure a service application. Haga clic en Next, asegúrese de que la opción Require Signatures está seleccionada para los mensajes de solicitud. (Según sean los requisitos se puede borrar la opción Require Encryption del mensaje de respuesta.)

En la siguiente pantalla, confirme el certificado X.509 Certificate como el tipo de token de cliente, deje vacía la lista de certificados de confianza que aparece en la siguiente pantalla y confirme y cierre la herramienta de configuración de seguridad de WSE.

Una única línea de código

Al principio de este artículo, mostré las dos formas distintas de utilizar la seguridad basada en funciones en aplicaciones ASP.NET clásicas: una estaba basada en código explícito para comprobar las funciones y la otra se basaba en el uso de seguridad declarativa con atributos de .NET para especificar las necesidades de seguridad de un método.

En las aplicaciones de servicios Web preparadas para WSE 2.0 se dispone de dos posibilidades similares. La principal diferencia es que no se van a utilizar atributos de .NET para designar las necesidades de seguridad del método, sino que se confiará en las funciones de WS-Policy. La razón de esta elección es que la directiva ofrece una enorme ventaja: es un archivo XML que se puede enviar al desarrollador que esté implementando un cliente de forma que pueda determinar por adelantado los requisitos necesarios que debe cumplir cada servicio Web.

Comenzaremos con las comprobaciones de pertenencia a funciones basadas en código. Desgraciadamente, ya no es posible usar la forma fácil, consistente en sólo utilizar una construcción similar a SomeContext.Current.User.IsInRole(), porque podría haber más de un token de seguridad en un mensaje SOAP entrante. En su lugar, lo que he decidido hacer es proporcionar dos métodos de aplicación auxiliar mientras que el primero comprueba el contexto de seguridad de la solicitud actual y busca un X509SecurityToken utilizado para firmar todo el cuerpo del mensaje entrante. La segunda aplicación auxiliar es un pequeño empaquetador para proporcionar una interfaz de programación tan fácil como utilizar SomeContext.Current.User.IsInRole().

public X509SecurityToken GetBodySigningToken(Security sec) 
{ 
  X509SecurityToken token = null; 
  foreach (ISecurityElement securityElement in sec.Elements) 
  { 
    if (securityElement is Signature) 
    { 
      Signature sig = (Signature)securityElement; 
      if ((sig.SignatureOptions & SignatureOptions.IncludeSoapBody) != 0) 
      { 
        SecurityToken sigToken = sig.SecurityToken; 
        if (sigToken is X509SecurityToken) 
        {
          token = (X509SecurityToken)sigToken;
        }
      } 
    } 
  } 
  return token; 
} 

private bool CurrentCertificatePrincipalIsInRole(String role)
{    
  X509SecurityToken tok = GetBodySigningToken(RequestSoapContext.Current.Security);
  if (tok == null) return false;
  if (tok.Principal == null) return false;
  return tok.Principal.IsInRole(role);
}

Ya se pueden utilizar estas aplicaciones auxiliares en [WebMethod] con una única línea de código para comprobar la pertenencia a las funciones del certificado X.509 del remitente:

[WebMethod]
public void SetMonthlySalary(long employeeID, double salary)
{
  if (! CurrentCertificatePrincipalIsInRole  ("HR"))
    throw new Exception("Only members of 'HR' may call this method.");

  if (salary > 2000)
  {
    if (! CurrentCertificatePrincipalIsInRole("PointyHairedBoss")) 
      throw new Exception("Only the pointy haired boss might set " + 
        "salaries larger than 2K");
  }

  // ... actual work removed ...

}

Comprobación de funciones con directiva del servidor

La parte final que falta es el uso de un archivo de directivas para especificar de forma declarativa las necesidades de seguridad del servicio Web. Para agregar esta "reclamación" al archivo de directivas, primero hay que abrir este archivo, que se ha agregado de forma automática al proyecto en el paso anterior. Normalmente este archivo se denomina policyCache.config.

En él, busque un perfil con el identificador "Sign-X.509". Dos niveles por abajo

de este elemento <Policy>, encontrará una ficha <wssp:SecurityToken>. Deber agregar un nodo relacionado a <wssp:TokenType> que especifique la reclamación solicitada. (Una reclamación de WS-Profile es esencialmente un "requisito" que un cierto token de cliente debe rellenar; en este caso, la pertenencia a una cierta función.)

En este caso, la reclamación es para <wse:Role> con un valor, value, de Accounting para asegurar que sólo los miembros de este grupo puedan invocar este servicio. Por lo tanto, el elemento <SecurityToken> completo tendrá el siguiente aspecto:

<wssp:SecurityToken wse:IdentityToken="true">
  <wssp:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-
                                                 profile-1.0#X509v3</wssp:TokenType>
  <wssp:Claims>
    <wse:Role 
      value="Accounting" 
      xmlns:wse="http://schemas.microsoft.com/wse/2003/06/Policy" />
  </wssp:Claims>
</wssp:SecurityToken>

Esta configuración hará que Framework compruebe todos los mensajes entrantes para los requisitos de seguridad mencionados en el archivo de directivas. Si por ejemplo no se ha firmado el mensaje entrante o si el certificado no está asignado a la función Accounting, se devolverá al cliente un error de SOAP, informándole de que el mensaje entrante no cumple con la directiva. Recuerde que si recibe una excepción informándole de que no se ha podido comprobar la cadena de confianza del certificado, se debe a que no se ha instalado el certificado raíz de la entidad emisora de certificados personalizada en el almacén "Equipo local". Para ello, vaya a http://<servidor_de_certificados>/certsrv e importe el certificado raíz seleccionando manualmente la ubicación de importación así como la ubicación física de importación en el asistente para la importación de certificados.

Resumen

En este artículo, se ha explicado cómo crear y utilizar un administrador de tokens de seguridad personalizado con Web Services Enhancements 2.0 para Microsoft .NET con el fin de comprobar certificados X.509, asignarles funciones y rellenar información contextual mediante objetos principal e identity personalizados. También se ha demostrado lo fácil que es utilizar WS-Policy desde Visual Studio .NET para agregar a las aplicaciones comprobaciones declarativas de pertenencia a funciones. La ventaja de este enfoque basado en WS-Security frente a la seguridad clásica basada en HTTP es que el primero no confía en la seguridad o integridad a nivel de transporte sino que trabaja únicamente con el mensaje SOAP. Esto ofrece capacidades de seguridad de un extremo a otro sobre varios saltos y protocolos.

Acerca del autor

Ingo Rammer es cofundador de thinktecture, una empresa que ofrece servicios de cursos de aprendizaje y consultoría técnica en profundidad para desarrolladores y arquitectos de software. Ingo es un renombrado experto en el diseño y desarrollo de aplicaciones distribuidas, y ofrece servicios de arquitectura, diseño y revisión de arquitectura para equipos de todos los tamaños.

Aparte de sus servicios de consultoría, es un orador habitual en conferencias de desarrolladores en todo el mundo y es el autor de dos libros con gran éxito de ventas, Advanced .NET Remoting y Advanced .NET Remoting in VB.NET (Apress). Ingo es el Director regional de Microsoft para Austria y recientemente ha sido galardonado con el estatus de Microsoft MVP de Arquitecto de soluciones. Puede ponerse en contacto con Ingo en http://www.thinktecture.com/staff/ingo (en inglés).

Mostrar:
© 2015 Microsoft