Authorization Policy

Cet exemple montre comment implémenter une stratégie d'autorisation de revendication personnalisée et un gestionnaire d'autorisations de service personnalisé associé. Cela s'avère utile lorsque le service procède à des vérifications d'accès basées sur des revendications sur des opérations de service et, avant d'effectuer ces vérifications, accorde certains droits à l'appelant. Cet exemple illustre à la fois le processus d'ajout de revendications ainsi que le processus de vérification de l'accès en fonction de l'ensemble de revendications finalisé. Tous les messages d'application échangés entre le client et le serveur sont signés et chiffrés. Par défaut, avec la liaison wsHttpBinding, un nom d’utilisateur et un mot de passe fournis par le client sont utilisés pour la connexion à un compte Windows valide. Cet exemple montre comment utiliser un UserNamePasswordValidator personnalisé pour authentifier le client. De plus, cet exemple montre comment le client s'authentifie auprès du service à l'aide d'un certificat X.509. Cet exemple montre une implémentation de IAuthorizationPolicy et ServiceAuthorizationManager, qui accordent à des utilisateurs spécifiques l'accès à des méthodes spécifiques du service. Cet exemple est basé sur l’exemple Message Security User Name, mais montre comment effectuer une transformation de revendication avant d'appeler ServiceAuthorizationManager.

Notes

La procédure d'installation ainsi que les instructions de génération relatives à cet exemple figurent à la fin de cette rubrique.

En résumé, cet exemple montre comment :

  • Le client peut être authentifié à l'aide d'un nom d'utilisateur et d'un mot de passe ;

  • Le client peut être authentifié à l'aide d'un certificat X.509 ;

  • Le serveur valide les informations d'identification du client en fonction d'un validateur UsernamePassword personnalisé ;

  • Le serveur est authentifié à l'aide du certificat X.509 du serveur.

  • Le serveur peut utiliser ServiceAuthorizationManager pour contrôler l'accès à certaines méthodes dans le service ;

  • Implémenter une interface IAuthorizationPolicy.

Le service expose deux points de terminaison utilisés pour la communication avec le service, définis à l’aide du fichier de configuration App.config. Chaque point de terminaison se compose d’une adresse, d’une liaison et d’un contrat. Une liaison est configurée avec une liaison wsHttpBinding standard qui utilise WS-Security et l’authentification du nom d’utilisateur du client. L'autre liaison est configurée avec une liaison wsHttpBinding standard qui utilise WS-Security et l'authentification du certificat du client. Le <comportement> spécifie que les informations d'identification de l'utilisateur seront utilisées à des fins d'authentification du service. Le certificat de serveur doit contenir la même valeur pour la propriété SubjectName que l'attribut findValue dans <serviceCertificate>.

<system.serviceModel>
  <services>
    <service name="Microsoft.ServiceModel.Samples.CalculatorService"
             behaviorConfiguration="CalculatorServiceBehavior">
      <host>
        <baseAddresses>
          <!-- configure base address provided by host -->
          <add baseAddress ="http://localhost:8001/servicemodelsamples/service"/>
        </baseAddresses>
      </host>
      <!-- use base address provided by host, provide two endpoints -->
      <endpoint address="username"
                binding="wsHttpBinding"
                bindingConfiguration="Binding1"
                contract="Microsoft.ServiceModel.Samples.ICalculator" />
      <endpoint address="certificate"
                binding="wsHttpBinding"
                bindingConfiguration="Binding2"
                contract="Microsoft.ServiceModel.Samples.ICalculator" />
    </service>
  </services>

  <bindings>
    <wsHttpBinding>
      <!-- Username binding -->
      <binding name="Binding1">
        <security mode="Message">
    <message clientCredentialType="UserName" />
        </security>
      </binding>
      <!-- X509 certificate binding -->
      <binding name="Binding2">
        <security mode="Message">
          <message clientCredentialType="Certificate" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>

  <behaviors>
    <serviceBehaviors>
      <behavior name="CalculatorServiceBehavior" >
        <serviceDebug includeExceptionDetailInFaults ="true" />
        <serviceCredentials>
          <!--
          The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.
          -->
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.ServiceModel.Samples.MyCustomUserNameValidator, service" />
          <!--
          The serviceCredentials behavior allows one to specify authentication constraints on client certificates.
          -->
          <clientCertificate>
            <!--
            Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate
            is in the user's Trusted People store, then it will be trusted without performing a
            validation of the certificate's issuer chain. This setting is used here for convenience so that the
            sample can be run without having to have certificates issued by a certification authority (CA).
            This setting is less secure than the default, ChainTrust. The security implications of this
            setting should be carefully considered before using PeerOrChainTrust in production code.
            -->
            <authentication certificateValidationMode="PeerOrChainTrust" />
          </clientCertificate>
          <!--
          The serviceCredentials behavior allows one to define a service certificate.
          A service certificate is used by a client to authenticate the service and provide message protection.
          This configuration references the "localhost" certificate installed during the setup instructions.
          -->
          <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
        </serviceCredentials>
        <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Samples.MyServiceAuthorizationManager, service">
          <!--
          The serviceAuthorization behavior allows one to specify custom authorization policies.
          -->
          <authorizationPolicies>
            <add policyType="Microsoft.ServiceModel.Samples.CustomAuthorizationPolicy.MyAuthorizationPolicy, PolicyLibrary" />
          </authorizationPolicies>
        </serviceAuthorization>
      </behavior>
    </serviceBehaviors>
  </behaviors>

</system.serviceModel>

Chaque configuration de point de terminaison de client se compose d'un nom de configuration, d'une adresse absolue pour le point de terminaison de service, de la liaison et du contrat. La liaison cliente est configurée avec le mode de sécurité approprié comme spécifié dans ce cas dans <security>, et clientCredentialType comme spécifié dans <message>.

<system.serviceModel>

    <client>
      <!-- Username based endpoint -->
      <endpoint name="Username"
            address="http://localhost:8001/servicemodelsamples/service/username"
    binding="wsHttpBinding"
    bindingConfiguration="Binding1"
                behaviorConfiguration="ClientCertificateBehavior"
                contract="Microsoft.ServiceModel.Samples.ICalculator" >
      </endpoint>
      <!-- X509 certificate based endpoint -->
      <endpoint name="Certificate"
                        address="http://localhost:8001/servicemodelsamples/service/certificate"
                binding="wsHttpBinding"
            bindingConfiguration="Binding2"
                behaviorConfiguration="ClientCertificateBehavior"
                contract="Microsoft.ServiceModel.Samples.ICalculator">
      </endpoint>
    </client>

    <bindings>
      <wsHttpBinding>
        <!-- Username binding -->
      <binding name="Binding1">
        <security mode="Message">
          <message clientCredentialType="UserName" />
        </security>
      </binding>
        <!-- X509 certificate binding -->
        <binding name="Binding2">
          <security mode="Message">
            <message clientCredentialType="Certificate" />
          </security>
        </binding>
    </wsHttpBinding>
    </bindings>

    <behaviors>
      <behavior name="ClientCertificateBehavior">
        <clientCredentials>
          <serviceCertificate>
            <!--
            Setting the certificateValidationMode to PeerOrChainTrust
            means that if the certificate
            is in the user's Trusted People store, then it will be
            trusted without performing a
            validation of the certificate's issuer chain. This setting
            is used here for convenience so that the
            sample can be run without having to have certificates
            issued by a certification authority (CA).
            This setting is less secure than the default, ChainTrust.
            The security implications of this
            setting should be carefully considered before using
            PeerOrChainTrust in production code.
            -->
            <authentication certificateValidationMode = "PeerOrChainTrust" />
          </serviceCertificate>
        </clientCredentials>
      </behavior>
    </behaviors>

  </system.serviceModel>

Pour le point de terminaison basé sur nom de l'utilisateur , l'implémentation cliente définit le nom d'utilisateur et le mot de passe à utiliser.

// Create a client with Username endpoint configuration
CalculatorClient client1 = new CalculatorClient("Username");

client1.ClientCredentials.UserName.UserName = "test1";
client1.ClientCredentials.UserName.Password = "1tset";

try
{
    // Call the Add service operation.
    double value1 = 100.00D;
    double value2 = 15.99D;
    double result = client1.Add(value1, value2);
    Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
    ...
}
catch (Exception e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
}

client1.Close();

Pour le point de terminaison basé sur certificat, l'implémentation cliente définit le certificat client à utiliser.

// Create a client with Certificate endpoint configuration
CalculatorClient client2 = new CalculatorClient("Certificate");

client2.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "test1");

try
{
    // Call the Add service operation.
    double value1 = 100.00D;
    double value2 = 15.99D;
    double result = client2.Add(value1, value2);
    Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
    ...
}
catch (Exception e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
}

client2.Close();

Cet exemple utilise un UserNamePasswordValidator personnalisé pour valider les noms d'utilisateur et les mots de passe. L'exemple implémente MyCustomUserNamePasswordValidator, dérivé de UserNamePasswordValidator. Pour plus d'informations, consultez la documentation relative au UserNamePasswordValidator. Pour les besoins de la démonstration de l'intégration avec le UserNamePasswordValidator, cet exemple de validateur personnalisé implémente la méthode Validate pour accepter des paires de nom d'utilisateur/mot de passe où le nom d'utilisateur correspond au mot de passe comme le montre le code suivant.

public class MyCustomUserNamePasswordValidator : UserNamePasswordValidator
{
  // This method validates users. It allows in two users,
  // test1 and test2 with passwords 1tset and 2tset respectively.
  // This code is for illustration purposes only and
  // MUST NOT be used in a production environment because it
  // is NOT secure.
  public override void Validate(string userName, string password)
  {
    if (null == userName || null == password)
    {
      throw new ArgumentNullException();
    }

    if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
    {
      throw new SecurityTokenException("Unknown Username or Password");
    }
  }
}

Une fois que le validateur est implémenté dans le code de service, l'hôte de service doit être informé de l'instance de validateur à utiliser. Cela s’effectue avec le code suivant :

Servicehost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyCustomUserNamePasswordValidatorProvider();

Vous pouvez arriver au même résultat dans la configuration :

<behavior>
    <serviceCredentials>
      <!--
      The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.
      -->
      <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.ServiceModel.Samples.MyCustomUserNameValidator, service" />
    ...
    </serviceCredentials>
</behavior>

Windows Communication Foundation (WCF) fournit un modèle basé sur des revendications enrichies pour effectuer des vérifications d'accès. L’objet ServiceAuthorizationManager est utilisé pour effectuer la vérification d’accès et détermine si les revendications associées au client répondent aux exigences nécessaires pour accéder à la méthode de service.

Pour les besoins de la démonstration, cet exemple illustre une implémentation de ServiceAuthorizationManager qui implémente la méthode CheckAccessCore pour autoriser l'accès d'un utilisateur aux méthodes basées sur des revendications de type http://example.com/claims/allowedoperation, dont la valeur est l'URI d'action de l'opération autorisée à être appelée.

public class MyServiceAuthorizationManager : ServiceAuthorizationManager
{
  protected override bool CheckAccessCore(OperationContext operationContext)
  {
    string action = operationContext.RequestContext.RequestMessage.Headers.Action;
    Console.WriteLine("action: {0}", action);
    foreach(ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
    {
      if ( cs.Issuer == ClaimSet.System )
      {
        foreach (Claim c in cs.FindClaims("http://example.com/claims/allowedoperation", Rights.PossessProperty))
        {
          Console.WriteLine("resource: {0}", c.Resource.ToString());
          if (action == c.Resource.ToString())
            return true;
        }
      }
    }
    return false;
  }
}

Une fois que le ServiceAuthorizationManager personnalisé est implémenté, l'hôte de service doit être informé du ServiceAuthorizationManager à utiliser. Cela est illustré dans le code suivant :

<behavior>
    ...
    <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Samples.MyServiceAuthorizationManager, service">
        ...
    </serviceAuthorization>
</behavior>

La méthode IAuthorizationPolicy principale à implémenter est la méthode Evaluate(EvaluationContext, Object).

public class MyAuthorizationPolicy : IAuthorizationPolicy
{
    string id;

    public MyAuthorizationPolicy()
    {
    id =  Guid.NewGuid().ToString();
    }

    public bool Evaluate(EvaluationContext evaluationContext,
                                            ref object state)
    {
        bool bRet = false;
        CustomAuthState customstate = null;

        if (state == null)
        {
            customstate = new CustomAuthState();
            state = customstate;
        }
        else
            customstate = (CustomAuthState)state;
        Console.WriteLine("In Evaluate");
        if (!customstate.ClaimsAdded)
        {
           IList<Claim> claims = new List<Claim>();

           foreach (ClaimSet cs in evaluationContext.ClaimSets)
              foreach (Claim c in cs.FindClaims(ClaimTypes.Name,
                                         Rights.PossessProperty))
                  foreach (string s in
                        GetAllowedOpList(c.Resource.ToString()))
                  {
                       claims.Add(new
               Claim("http://example.com/claims/allowedoperation",
                                    s, Rights.PossessProperty));
                            Console.WriteLine("Claim added {0}", s);
                      }
                   evaluationContext.AddClaimSet(this,
                           new DefaultClaimSet(this.Issuer,claims));
                   customstate.ClaimsAdded = true;
                   bRet = true;
                }
         else
         {
              bRet = true;
         }
         return bRet;
     }
...
}

Le code précédent montre comment la méthode Evaluate(EvaluationContext, Object) vérifie qu'aucune nouvelle revendication n'a été ajoutée qui affecte le traitement et ajoute des revendications spécifiques. Les revendications autorisées sont obtenues de la méthode GetAllowedOpList, implémentée pour retourner une liste spécifique d'opérations que l'utilisateur est autorisé à effectuer. La stratégie d'autorisation ajoute des revendications pour l'accès à l'opération particulière. Elle est utilisée ultérieurement par le ServiceAuthorizationManager pour exécuter des décisions relatives à la vérification de l'accès.

Une fois la IAuthorizationPolicy personnalisée implémentée, l'hôte de service doit être informé des stratégies d'autorisation à utiliser.

<serviceAuthorization>
       <authorizationPolicies>
            <add policyType='Microsoft.ServiceModel.Samples.CustomAuthorizationPolicy.MyAuthorizationPolicy, PolicyLibrary' />
       </authorizationPolicies>
</serviceAuthorization>

Lorsque vous exécutez l'exemple, les demandes et réponses d'opération s'affichent dans la fenêtre de console du client. Le client appelle avec succès les méthodes d'addition, de soustraction et de multiplication, et obtient le message « Accès refusé » lors de la tentative d'appel de la méthode de division. Appuyez sur Entrée dans la fenêtre du client pour l'arrêter.

Fichier de commandes d'installation

Le fichier de commandes Setup.bat inclus avec cet exemple permet de configurer le serveur avec les certificats pertinents pour exécuter une application auto-hébergée qui requiert une sécurité basée sur le certificat du serveur.

Les éléments suivants fournissent une brève vue d'ensemble des différentes sections des fichiers de commandes afin qu'ils puissent être modifiés pour s'exécuter dans la configuration appropriée :

  • Création du certificat de serveur

    Les lignes suivantes du fichier de commandes Setup.bat créent le certificat de serveur à utiliser. La variable %SERVER_NAME% spécifie le nom du serveur. Modifiez cette variable pour spécifier votre propre nom de serveur. La valeur par défaut est localhost.

    echo ************
    echo Server cert setup starting
    echo %SERVER_NAME%
    echo ************
    echo making server cert
    echo ************
    makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
    
  • Installation du certificat de serveur dans le magasin de certificats approuvé du client.

    Les lignes suivantes du fichier de commandes Setup.bat copient le certificat de serveur dans le magasin de personnes de confiance du client. Cette étape est requise car les certificats générés par Makecert.exe ne sont pas implicitement approuvés par le système client. Si vous disposez déjà d'un certificat associé à un certificat racine approuvé du client, par exemple un certificat émis par Microsoft, cette étape de remplissage du magasin de certificats client avec le certificat de serveur n'est pas requise.

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • Création du certificat client

    Les lignes suivantes du fichier de commandes Setup.bat créent le certificat client à utiliser. La variable %USER_NAME% spécifie le nom du serveur. Cette valeur est « test1 » parce que c'est le nom que la IAuthorizationPolicy recherche. Si vous modifiez la valeur de %USER_NAME%, vous devez modifier la valeur correspondante dans la méthode IAuthorizationPolicy.Evaluate.

    Le certificat est stocké dans le magasin My (Personal) sous l'emplacement de magasin CurrentUser.

    echo ************
    echo making client cert
    echo ************
    makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe
    
  • Installation du certificat client dans le magasin de certificats approuvés du serveur.

    Les lignes suivantes du fichier de commandes Setup.bat copient le certificat client dans le magasin de personnes de confiance du client. Cette étape est requise car les certificats générés par Makecert.exe ne sont pas implicitement approuvés par le système du serveur. Si vous disposez déjà d'un certificat associé à un certificat racine approuvé du client, par exemple un certificat émis par Microsoft, cette étape de remplissage du magasin de certificats du serveur avec le certificat client n'est pas requise.

    certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople
    

Pour configurer et générer l'exemple

  1. Pour générer la solution, suivez les instructions indiquées dans la rubrique Génération des exemples Windows Communication Foundation.

  2. Pour exécuter l'exemple dans une configuration à un ou plusieurs ordinateurs, suivez les instructions ci-dessous.

Notes

Si vous utilisez Svcutil.exe pour régénérer la configuration pour cet exemple, assurez-vous de modifier le nom du point de terminaison dans la configuration client afin qu'il corresponde au code client.

Pour exécuter l'exemple sur le même ordinateur

  1. Ouvrez une invite de commandes développeur pour Visual Studio avec des privilèges d’administrateur, puis exécutez Setup.bat à partir du dossier d’installation de l’exemple. Tous les certificats requis à l'exécution de l'exemple sont ainsi installés.

    Notes

    Le fichier de commandes Setup.bat est conçu pour être exécuté à partir d’une invite de commandes développeur pour Visual Studio. La variable d’environnement PATH définie dans l’invite de commandes développeur pour Visual Studio pointe vers le répertoire qui contient les exécutables requis par le script Setup.bat.

  2. Lancez Service.exe à partir de service\bin.

  3. Lancez Client.exe à partir de \client\bin. L'activité du client s'affiche sur son application de console.

Si le client et le service ne parviennent pas à communiquer, consultez Conseils de dépannage pour les exemples WCF.

Pour exécuter l'exemple sur plusieurs ordinateurs

  1. Créez un répertoire sur l'ordinateur de service.

  2. Copiez les fichiers programme du service de \service\bin vers le répertoire sur l'ordinateur de service. Copiez également les fichiers Setup.bat, Cleanup.bat, GetComputerName.vbs et ImportClientCert.bat sur l'ordinateur de service.

  3. Créez un répertoire sur l'ordinateur client pour les fichiers binaires du client.

  4. Copiez les fichiers programme du client dans le répertoire client de l'ordinateur client. Copiez également les fichiers Setup.bat, Cleanup.bat et ImportServiceCert.bat sur le client.

  5. Sur le serveur, exécutez setup.bat service à partir d’une invite de commandes développeur pour Visual Studio ouverte avec des privilèges d’administrateur.

    L’exécution de setup.bat avec l’argument service crée un certificat de service avec le nom de domaine complet de l’ordinateur, puis exporte ce certificat vers un fichier nommé Service.cer.

  6. Modifiez Service.exe.config pour refléter le nouveau nom du certificat (dans l’attribut findValue dans <serviceCertificate>), qui est identique au nom de domaine complet de l’ordinateur. Remplacez également la valeur computername dans l’élément <service>/<baseAddresses> de localhost par le nom complet de votre ordinateur de service.

  7. Copiez le fichier Service.cer du répertoire de service vers le répertoire client sur l'ordinateur client.

  8. Sur le client, exécutez setup.bat client à partir d’une invite de commandes développeur pour Visual Studio ouverte avec des privilèges d’administrateur.

    L'exécution de setup.bat avec l'argument client crée un certificat client appelé test1, puis exporte ce certificat vers un fichier nommé Client.cer.

  9. Dans le fichier Client.exe.config de l'ordinateur client, modifiez l'adresse du point de terminaison afin qu'elle corresponde à la nouvelle adresse de votre service. Pour ce faire, remplacez localhost par le nom de domaine complet du serveur.

  10. Copiez le fichier Client.cer du répertoire client dans le répertoire de service sur le serveur.

  11. Sur le client, exécutez ImportServiceCert.bat à partir d’une invite de commandes développeur pour Visual Studio ouverte avec des privilèges d’administrateur.

    Cette opération importe le certificat de service du fichier Service.cer vers le magasin CurrentUser - TrustedPeople.

  12. Sur le serveur, exécutez ImportClientCert.bat à partir d’une invite de commandes développeur pour Visual Studio ouverte avec des privilèges d’administrateur.

    Cette opération importe le certificat client du fichier Client.cer dans le magasin LocalMachine - TrustedPeople.

  13. Sur l'ordinateur serveur, lancez Service.exe à partir de la fenêtre d'invite de commandes.

  14. Sur l'ordinateur client, lancez Client.exe à partir d'une fenêtre d'invite de commandes.

    Si le client et le service ne parviennent pas à communiquer, consultez Conseils de dépannage pour les exemples WCF.

Nettoyage après exécution de l'exemple

Pour procéder au nettoyage après exécution de l'exemple, exécutez Cleanup.bat dans le dossier d’exemples lorsque vous avez terminé d’exécuter l’exemple. Cela supprime les certificats du serveur et du client du magasin de certificats.

Notes

Ce script ne supprime pas de certificat de service sur un client lors de l'exécution de cet exemple sur plusieurs ordinateurs. Si vous avez exécuté des exemples WCF qui utilisaient des certificats sur plusieurs ordinateurs, assurez-vous de supprimer les certificats de service installés dans le magasin CurrentUser - TrustedPeople. Pour ce faire, utilisez la commande suivante : certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name>, par exemple : certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.