Token di supporto

L'esempio dei token di supporto illustra come aggiungere token aggiuntivi a un messaggio che utilizza WS-Security. L'esempio aggiunge un token di protezione binario X.509 e un token di protezione nome utente. Il token viene passato in un'intestazione di un messaggio WS-Security dal client al servizio e parte del messaggio viene firmata con la chiave privata associata al token di protezione X.509 per provare il possesso del certificato X.509 al destinatario. Ciò è utile nel caso in cui vi sia un requisito di più richieste per autenticare o autorizzare il mittente associate a un messaggio. Il servizio implementa un contratto che definisce il modello di comunicazione request/reply.

Dimostrazione

L'esempio illustra quanto segue:

  • Comeun client può passare token di protezione aggiuntivi a un servizio.
  • Come il server può accedere a richieste associate ai token di protezione aggiuntivi.
  • Come viene utilizzato il certificato X.509 del server per proteggere la chiave simmetrica utilizzata per crittografare il messaggio e la firma.

Nota

La procedura di installazione e le istruzioni di compilazione per questo esempio si trovano alla fine di questo argomento.

Il client autentica con token nome utente e token di protezione X.509 di supporto

Il servizio espone un solo endpoint per comunicare con il servizio che viene creato a livello di codice utilizzando le classi BindingHelper e EchoServiceHost. L'endpoint è costituito da un indirizzo, un'associazione e un contratto. L'associazione è configurata con un'associazione personalizzata utilizzando SymmetricSecurityBindingElement e HttpTransportBindingElement. Questo esempio imposta SymmetricSecurityBindingElement per utilizzare un certificato X.509 del servizio per proteggere la chiave simmetrica durante la trasmissione e passare un UserNameToken insieme a un X509SecurityToken di supporto in un'intestazione del messaggio WS-Security. La chiave simmetrica viene utilizzata per crittografare il corpo del messaggio e il token di protezione del nome utente. Il token di supporto viene passato come un token di protezione binario aggiuntivo nell'intestazione del messaggio WS-Security. L'autenticità del token di supporto viene provata firmando parte del messaggio con la chiave privata associata con il token di protezione X.509 di supporto.

        public static Binding CreateMultiFactorAuthenticationBinding()
        {
            HttpTransportBindingElement httpTransport = new HttpTransportBindingElement();

            // the message security binding element will be configured to require 2 tokens:
            // 1) A username-password encrypted with the service token
            // 2) A client certificate used to sign the message
            
            // Instantiate a binding element that will require the username/password token in the message (encrypted with the server cert)
            SymmetricSecurityBindingElement messageSecurity = SecurityBindingElement.CreateUserNameForCertificateBindingElement();

            // Create supporting token parameters for the client X509 certificate.
            X509SecurityTokenParameters clientX509SupportingTokenParameters = new X509SecurityTokenParameters();
            // Specify that the supporting token is passed in message send by the client to the service
            clientX509SupportingTokenParameters.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
            // Turn off derived keys
            clientX509SupportingTokenParameters.RequireDerivedKeys = false;
            // Augment the binding element to require the client's X509 certificate as an endorsing token in the message
            messageSecurity.EndpointSupportingTokenParameters.Endorsing.Add(clientX509SupportingTokenParameters);

            // Create a CustomBinding based on the constructed security binding element.
            return new CustomBinding(messageSecurity, httpTransport);
        }

Il comportamento specifica le credenziali del servizio che devono essere utilizzate per l'autenticazione del client e anche informazioni sul certificato X.509 del servizio. L'esempio utilizza CN=localhost come nome del soggetto nel certificato X.509 del servizio.

override protected void InitializeRuntime()
{
    // Extract the ServiceCredentials behavior or create one.
    ServiceCredentials serviceCredentials = 
        this.Description.Behaviors.Find<ServiceCredentials>();
    if (serviceCredentials == null)
    {
        serviceCredentials = new ServiceCredentials();
        this.Description.Behaviors.Add(serviceCredentials);
    }

    // Set the service certificate
    serviceCredentials.ServiceCertificate.SetCertificate(
                                       "CN=localhost");

/*
Setting the CertificateValidationMode to PeerOrChainTrust means that if the certificate is in the 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 certificate 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.
*/
    serviceCredentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.PeerOrChainTrust;

    // Create the custom binding and add an endpoint to the service.
    Binding multipleTokensBinding =
         BindingHelper.CreateMultiFactorAuthenticationBinding();
    this.AddServiceEndpoint(typeof(IEchoService), 
                          multipleTokensBinding, string.Empty);
    base.InitializeRuntime();
}

Codice del servizio

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class EchoService : IEchoService
{
    public string Echo()
    {
        string userName;
        string certificateSubjectName;
        GetCallerIdentities(
            OperationContext.Current.ServiceSecurityContext, 
            out userName, 
            out certificateSubjectName);
            return String.Format("Hello {0}, {1}", 
                    userName, certificateSubjectName);
    }

    public void Dispose()
    {
    }


    bool TryGetClaimValue<TClaimResource>(ClaimSet claimSet, 
            string claimType, out TClaimResource resourceValue)
            where TClaimResource : class
    {
        resourceValue = default(TClaimResource);
        IEnumerable<Claim> matchingClaims = 
            claimSet.FindClaims(claimType, Rights.PossessProperty);
        if(matchingClaims == null)
            return false;
        IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
        if (enumerator.MoveNext())
        {
            resourceValue = 
              (enumerator.Current.Resource == null) ? null : 
              (enumerator.Current.Resource as TClaimResource);
            return true;
        }
        else
        {
            return false;
        }
    }

    // Returns the username and certificate subject name provided by 
    //the client
    void GetCallerIdentities(ServiceSecurityContext 
        callerSecurityContext, 
        out string userName, out string certificateSubjectName)
    {
        userName = null;
        certificateSubjectName = null;

       // Look in all the claimsets in the authorization context
       foreach (ClaimSet claimSet in 
               callerSecurityContext.AuthorizationContext.ClaimSets)
       {
            if (claimSet is WindowsClaimSet)
            {
                // Try to find a Name claim. This will have been 
                // generated from the windows username.
                string tmpName;
                if (TryGetClaimValue<string>(claimSet, ClaimTypes.Name, 
                                                      out tmpName))
                {
                    userName = tmpName;
                }
            }
            else if (claimSet is X509CertificateClaimSet)
            {
                // Try to find an X500DisinguishedName claim. This will 
                // have been generated from the client certificate.
                X500DistinguishedName tmpDistinguishedName;
                if (TryGetClaimValue<X500DistinguishedName>(claimSet, 
                               ClaimTypes.X500DistinguishedName, 
                               out tmpDistinguishedName))
                {
                    certificateSubjectName = tmpDistinguishedName.Name;
                }
            }
        }
    }
} 

L'endpoint del client viene configurato in modo simile all'endpoint del servizio. Il client utilizza la stessa classe BindingHelper per creare un'associazione. Il resto dell'installazione è situato nella classe Client. Il client imposta informazioni sul token di protezione del nome utente, il token di protezione X.509 di supporto e informazioni sul certificato X.509 del servizio nel codice dell'installazione sull'insieme dei comportamenti dell'endpoint del client.

 static void Main()
 {
     // Create the custom binding and an endpoint address for 
     // the service.
     Binding multipleTokensBinding = 
         BindingHelper.CreateMultiFactorAuthenticationBinding();
         EndpointAddress serviceAddress = new EndpointAddress(
         "https://localhost/servicemodelsamples/service.svc");
       ChannelFactory<IEchoService> channelFactory = null;
       IEchoService client = null;

       Console.WriteLine("Username authentication required.");
       Console.WriteLine(
         "Provide a valid machine or domain account. [domain\\user]");
       Console.WriteLine("   Enter username:");
       string username = Console.ReadLine();
       Console.WriteLine("   Enter password:");
       string password = "";
       ConsoleKeyInfo info = Console.ReadKey(true);
       while (info.Key != ConsoleKey.Enter)
       {
           if (info.Key != ConsoleKey.Backspace)
           {
               if (info.KeyChar != '\0')
               {
                   password += info.KeyChar;
                }
                info = Console.ReadKey(true);
            }
            else if (info.Key == ConsoleKey.Backspace)
            {
                if (password != "")
                {
                    password = 
                       password.Substring(0, password.Length - 1);
                }
                info = Console.ReadKey(true);
            }
         }
         for (int i = 0; i < password.Length; i++)
            Console.Write("*");
         Console.WriteLine();
         try
         {
           // Create a proxy with the previously create binding and 
           // endpoint address
              channelFactory = 
                 new ChannelFactory<IEchoService>(
                     multipleTokensBinding, serviceAddress);
           // configure the username credentials, the client 
           // certificate and the server certificate on the channel 
           // factory 
           channelFactory.Credentials.UserName.UserName = username;
           channelFactory.Credentials.UserName.Password = password;
           channelFactory.Credentials.ClientCertificate.SetCertificate(
           "CN=client.com", StoreLocation.CurrentUser, StoreName.My);
              channelFactory.Credentials.ServiceCertificate.SetDefaultCertificate(
           "CN=localhost", StoreLocation.LocalMachine, StoreName.My);
           client = channelFactory.CreateChannel();
           Console.WriteLine("Echo service returned: {0}", 
                                           client.Echo());

           ((IChannel)client).Close();
           channelFactory.Close();
        }
        catch (CommunicationException e)
        {
         Abort((IChannel)client, channelFactory);
         // if there is a fault then print it out
         FaultException fe = null;
         Exception tmp = e;
         while (tmp != null)
         {
            fe = tmp as FaultException;
            if (fe != null)
            {
                break;
            }
            tmp = tmp.InnerException;
        }
        if (fe != null)
        {
           Console.WriteLine("The server sent back a fault: {0}", 
         fe.CreateMessageFault().Reason.GetMatchingTranslation().Text);
        }
        else
        {
         Console.WriteLine("The request failed with exception: {0}",e);
        }
    }
    catch (TimeoutException)
    {
        Abort((IChannel)client, channelFactory);
        Console.WriteLine("The request timed out");
    }
    catch (Exception e)
    {
         Abort((IChannel)client, channelFactory);
          Console.WriteLine(
          "The request failed with unexpected exception: {0}", e);
    }
    Console.WriteLine();
    Console.WriteLine("Press <ENTER> to terminate client.");
    Console.ReadLine();
}

Visualizzazione delle informazioni sul chiamante

Per visualizzare le informazioni sul chiamante è possibile utilizzare ServiceSecurityContext.Current.AuthorizationContext.ClaimSets come mostra il codice seguente. ServiceSecurityContext.Current.AuthorizationContext.ClaimSets contiene richieste di autorizzazione associate al chiamante corrente. Tali richieste vengono fornite automaticamente da Windows Communication Foundation (WCF) per ogni token ricevuto nel messaggio.

bool TryGetClaimValue<TClaimResource>(ClaimSet claimSet, string 
                         claimType, out TClaimResource resourceValue)
    where TClaimResource : class
{
    resourceValue = default(TClaimResource);
    IEnumerable<Claim> matchingClaims = 
    claimSet.FindClaims(claimType, Rights.PossessProperty);
    if (matchingClaims == null)
          return false;
    IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
    if (enumerator.MoveNext())
    {
        resourceValue = (enumerator.Current.Resource == null) ? null : (enumerator.Current.Resource as TClaimResource);
        return true;
    }
    else
    {
         return false;
    }
}

// Returns the username and certificate subject name provided by the client
void GetCallerIdentities(ServiceSecurityContext callerSecurityContext, out string userName, out string certificateSubjectName)
{
    userName = null;
    certificateSubjectName = null;

    // Look in all the claimsets in the authorization context
    foreach (ClaimSet claimSet in 
      callerSecurityContext.AuthorizationContext.ClaimSets)
    {
        if (claimSet is WindowsClaimSet)
        {
            // Try to find a Name claim. This will have been generated 
            //from the windows username.
            string tmpName;
            if (TryGetClaimValue<string>(claimSet, ClaimTypes.Name, 
                                                     out tmpName))
            {
                userName = tmpName;
            }
        }
        else if (claimSet is X509CertificateClaimSet)
         {
            //Try to find an X500DisinguishedName claim. 
            //This will have been generated from the client 
            //certificate.
            X500DistinguishedName tmpDistinguishedName;
            if (TryGetClaimValue<X500DistinguishedName>(claimSet, 
               ClaimTypes.X500DistinguishedName, 
               out tmpDistinguishedName))
            {
                    certificateSubjectName = tmpDistinguishedName.Name;
            }
        }
    }
}

Esecuzione dell'esempio

Quando si esegue l'esempio, il client innanzitutto richiede nome utente e password per il token del nome utente. Assicurarsi di fornire valori corretti per l'account del sistema, perché WCF nel servizio esegue il mapping dei valori forniti nel token del nome utente nell'identità fornita dal sistema. Successivamente, il client visualizza la risposta del servizio. Premere INVIO nella finestra del client per arrestare il client.

File batch di installazione

Il file batch Setup.bat incluso in questo esempio consente di configurare il server con i certificati attinenti per eseguire un'applicazione ospitata su Internet Information Services (IIS) che richiede protezione server basata su certificato. Questo file batch deve essere modificato per funzionare su computer diversi o per operare in applicazioni indipendenti.

Di seguito viene fornita una breve panoramica delle varie sezioni dei file batch così che possono essere modificati per l'esecuzione nella configurazione appropriata.

Creazione del certificato del client

Le righe seguenti del file batch Setup.bat creano il certificato client da utilizzare. La variabile %CLIENT_NAME% specifica l'oggetto del certificato client. Questo esempio utilizza "client.com" come nome del soggetto.

Il certificato viene memorizzato nell'archivio personale nel percorso di archivio CurrentUser.

echo ************
echo making client cert
echo ************
makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe

Installazione del certificato client nell'archivio certificati attendibili del server:

La riga seguente nel file batch Setup.bat copia il certificato client nell'archivio delle persone attendibili del server. Questo passaggio è necessario perché certificati generati da Makecert.exe non sono considerati implicitamente attendibili dal sistema server. Se è già disponibile un certificato impostato come radice in un certificato radice client attendibile, ad esempio un certificato rilasciato da Microsoft, il passaggio della popolazione dell'archivio certificati client con il certificato server non è necessario.

echo ************
echo copying client cert to server's CurrentUserstore
echo ************
certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople

Creazione del certificato del server:

Le righe seguenti del file batch Setup.bat creano il certificato server da utilizzare. La variabile %SERVER_NAME% specifica il nome del server. Modificare questa variabile per specificare nome del server. Il valore predefinito in questo file batch è localhost.

Il certificato viene memorizzato nell'archivio personale nel percorso di archivio LocalMachine. Il certificato è archiviato in LocalMachine per i servizi ospitati su IIS. Per i servizi indipendenti, è necessario modificare il file batch per archiviare il certificato server nel percorso dell'archivio CurrentUser sostituendo la stringa LocalMachine con CurrentUser.

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

Installazione del certificato server nell'archivio certificati attendibili del client:

Le righe seguenti nel file batch Setup.bat copiano il certificato server nell'archivio delle persone attendibile del client. Questo passaggio è necessario perché certificati generati da Makecert.exe non sono considerati implicitamente attendibili dal sistema client. Se è già disponibile un certificato impostato come radice in un certificato radice client attendibile, ad esempio un certificato rilasciato da Microsoft, il passaggio della popolazione dell'archivio certificati client con il certificato server non è necessario.

echo ************
echo copying server cert to client's TrustedPeople store
echo ************certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople

Abilitare l'accesso alla chiave privata del certificato

Per abilitare l'accesso alla chiave privata del certificato dal servizio ospitato su IIS, l'account utente con cui il processo ospitato da IIS è eseguito, deve avere autorizzazioni appropriate alla chiave privata. Ciò viene eseguito negli ultimi passaggi dello script Setup.bat.

echo ************
echo setting privileges on server certificates
echo ************
for /F "delims=" %%i in ('"%ProgramFiles%\ServiceModelSampleTools\FindPrivateKey.exe" My LocalMachine -n CN^=%SERVER_NAME% -a') do set PRIVATE_KEY_FILE=%%i
set WP_ACCOUNT=NT AUTHORITY\NETWORK SERVICE
(ver | findstr /C:"5.1") && set WP_ACCOUNT=%COMPUTERNAME%\ASPNET
echo Y|cacls.exe "%PRIVATE_KEY_FILE%" /E /G "%WP_ACCOUNT%":R
iisreset

Per impostare, compilare ed eseguire l'esempio

  1. Assicurarsi di aver eseguito Procedura di installazione singola per gli esempi di Windows Communication Foundation.

  2. Per generare la soluzione, seguire le istruzioni in Generazione degli esempi Windows Communication Foundation.

  3. Per eseguire l'esempio su una configurazione con un solo computer o tra computer diversi, seguire le istruzioni seguenti.

Per eseguire l'esempio sullo stesso computer

  1. Verificare che il percorso includa la cartella in cui è situato Makecert.exe.

Nota

Il file batch Setup.bat è progettato per essere eseguito da un prompt dei comandi di Windows SDK.

  1. Eseguire Setup.bat dalla cartella di installazione dell'esempio. In tal modo vengono installati tutti i certificati necessari per l'esecuzione dell'esempio.

Nota

Assicurarsi di rimuovere i certificati eseguendo Cleanup.bat una volta completato l'esempio. Gli altri esempi relativi alla protezione utilizzano gli stessi certificati.

  1. Avviare Client.exe da \client\bin. L'attività del client viene visualizzata nella finestra dell'applicazione console.
  2. Se il client e il servizio non sono in grado di comunicare, vedere Suggerimenti per la risoluzione dei problemi.

Per eseguire l'esempio tra più computer

  1. Creare una directory sul computer del servizio. Crea un'applicazione virtuale denominata servicemodelsamples per questa directory utilizzando lo strumento di gestione di Internet Information Services (IIS).

  2. Copiare i file del programma del servizio da \inetpub\wwwroot\servicemodelsamples alla directory virtuale sul computer del servizio. Assicurarsi di copiare i file nella sottodirectory \bin. Copiare anche i file Setup.bat, Cleanup.bat e ImportClientCert.bat nel computer del servizio.

  3. Creare una directory sul client del servizio per i file binari del client.

  4. Copiare i file di programma del client nella directory del client sul computer del client. Copiare anche i file Setup.bat, Cleanup.bat e ImportServiceCert.bat nel computer del client.

  5. Nel server, eseguire setup.bat servizio. Eseguendo setup.bat con l'argomento service si crea un certificato del servizio con il nome di dominio completo del computer e si esporta il certificato del servizio in un file denominato Service.cer.

  6. Modificare Web.config per riflettere il nuovo nome del certificato (nell'attributo findValue in serviceCertificate element of serviceCredentials) che corrisponde al nome di dominio completo del computer.

  7. Copiare il file Service.cer dalla directory del servizio alla directory del client sul computer client.

  8. Nel client, eseguire setup.bat client. Eseguendosetup.bat con l'argomento client si crea un certificato client denominato client.com e si esporta il certificato client in un file denominato Client.cer.

  9. Nel file Client.exe.config nel computer client, modificare il valore dell'indirizzo della definizione dell'endpoint in base al nuovo indirizzo del servizio. Tale operazione viene eseguita sostituendo localhost con il nome di dominio completo del server.

  10. Copiare il file Client.cer dalla directory del client alla directory del servizio sul server.

  11. Nel client, eseguire ImportServiceCert.bat. In questo modo viene importato il certificato del servizio dal file Service.cer nell'archivio CurrentUser - TrustedPeople.

  12. Eseguire sul server ImportClientCert.bat. In questo modo il certificato client viene importato dal file Client.cer nell'archivio LocalMachine - TrustedPeople.

  13. Sul computer client, avviare Client.exe da una finestra del prompt dei comandi. Se il client e il servizio non sono in grado di comunicare, vedere Suggerimenti per la risoluzione dei problemi.

Per eseguire la pulitura dopo l'esempio

  • Eseguire Cleanup.bat nella cartella degli esempi dopo che è terminata l'esecuzione dell'esempio.

Nota

Questo script non rimuove i certificati del servizio su un client quando si esegue questo esempio tra più computer. Se sono stati eseguiti esempi di WCF che utilizzano certificati tra più computer, assicurarsi di cancellare i certificati del servizio installati nell'archivio CurrentUser - TrustedPeople. Per eseguire questa operazione, utilizzare il seguente comando: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Ad esempio: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.

Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.