Stödtokens

Exemplet Stödtoken visar hur du lägger till ytterligare token i ett meddelande som använder WS-Security. Exemplet lägger till en binär X.509-säkerhetstoken utöver en säkerhetstoken för användarnamn. Token skickas i ett WS-Security meddelandehuvud från klienten till tjänsten och en del av meddelandet signeras med den privata nyckeln som är associerad med X.509-säkerhetstoken för att bevisa innehavet av X.509-certifikatet till mottagaren. Detta är användbart när det finns ett krav på att ha flera anspråk associerade med ett meddelande för att autentisera eller auktorisera avsändaren. Tjänsten implementerar ett kontrakt som definierar ett kommunikationsmönster för begäran-svar.

Demonstrerar

Exemplet visar:

  • Hur en klient kan skicka ytterligare säkerhetstoken till en tjänst.

  • Hur servern kan komma åt anspråk som är associerade med ytterligare säkerhetstoken.

  • Hur serverns X.509-certifikat används för att skydda den symmetriska nyckel som används för meddelandekryptering och signatur.

Anmärkning

Installationsproceduren och bygginstruktionerna för det här exemplet finns i slutet av det här avsnittet.

Klienten autentiserar med användarnamnstoken och stöd för X.509-säkerhetstoken

Tjänsten exponerar en enskild slutpunkt för kommunikation som skapas programmatiskt med hjälp av klasserna BindingHelper och EchoServiceHost . Slutpunkten består av en adress, en bindning och ett kontrakt. Bindningen konfigureras med en anpassad bindning med hjälp av SymmetricSecurityBindingElement och HttpTransportBindingElement. Det här exemplet anger SymmetricSecurityBindingElement att använda ett X.509-tjänstcertifikat för att skydda den symmetriska nyckeln under överföringen och för att skicka en UserNameToken tillsammans med stödjande X509SecurityToken i ett WS-Security-meddelandehuvud. Den symmetriska nyckeln används för att kryptera meddelandetexten och användarnamnets säkerhetstoken. Den stödjande token skickas som en ytterligare binär säkerhetstoken i meddelanderubriken WS-Security. Autenticiteten för den stödjande token bevisas genom att en del av meddelandet signeras med den privata nyckeln som är associerad med den stödda X.509-säkerhetstoken.

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);
}

Beteendet anger de tjänstautentiseringsuppgifter som ska användas för klientautentisering och även information om X.509-tjänstcertifikatet. Exemplet använder CN=localhost som ämnesnamn i tjänst-X.509-certifikatet.

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 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.
*/
    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();
}

Tjänstkod:

[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class EchoService : IEchoService
{
    public string Echo()
    {
        string userName;
        string certificateSubjectName;
        GetCallerIdentities(
            OperationContext.Current.ServiceSecurityContext,
            out userName,
            out certificateSubjectName);
            return $"Hello {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 X500DistinguishedName claim. This will
                // have been generated from the client certificate.
                X500DistinguishedName tmpDistinguishedName;
                if (TryGetClaimValue<X500DistinguishedName>(claimSet,
                               ClaimTypes.X500DistinguishedName,
                               out tmpDistinguishedName))
                {
                    certificateSubjectName = tmpDistinguishedName.Name;
                }
            }
        }
    }
}

Klientslutpunkten konfigureras på ett liknande sätt som tjänstslutpunkten. Klienten använder samma BindingHelper klass för att skapa en bindning. Resten av installationen finns i Client klassen. Klienten anger information om användarnamnets säkerhetstoken, den stödda X.509-säkerhetstoken och information om tjänst-X.509-certifikatet i installationskoden till samlingen med klientslutpunktsbeteenden.

static void Main()
 {
     // Create the custom binding and an endpoint address for
     // the service.
     Binding multipleTokensBinding =
         BindingHelper.CreateMultiFactorAuthenticationBinding();
         EndpointAddress serviceAddress = new EndpointAddress(
         "http://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();
}

Visa uppringarens information

Om du vill visa uppringarens information kan du använda det ServiceSecurityContext.Current.AuthorizationContext.ClaimSets som visas i följande kod. Den ServiceSecurityContext.Current.AuthorizationContext.ClaimSets innehåller auktoriseringsanspråk som är associerade med den aktuella anroparen. Dessa anspråk tillhandahålls automatiskt av Windows Communication Foundation (WCF) för varje mottagen token i meddelandet.

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 X500DistinguishedName claim.
            //This will have been generated from the client
            //certificate.
            X500DistinguishedName tmpDistinguishedName;
            if (TryGetClaimValue<X500DistinguishedName>(claimSet,
               ClaimTypes.X500DistinguishedName,
               out tmpDistinguishedName))
            {
                    certificateSubjectName = tmpDistinguishedName.Name;
            }
        }
    }
}

Körning av provet

När du kör exemplet uppmanas du först att ange användarnamn och lösenord för användarnamnstoken. Se till att ange rätt värden för ditt systemkonto eftersom WCF på tjänsten mappar värdena som anges i användarnamnstoken till den identitet som tillhandahålls av systemet. Därefter visar klienten svaret från tjänsten. Tryck på RETUR i klientfönstret för att stänga av klienten.

Konfigurera Batch-fil

Med Setup.bat batchfil som ingår i det här exemplet kan du konfigurera servern med relevanta certifikat för att köra IIS-värdprogram (Internet Information Services) som kräver servercertifikatbaserad säkerhet. Den här batchfilen måste ändras för att fungera mellan datorer eller för att fungera i ett fall som inte är värdbaserat.

Följande ger en kort översikt över de olika avsnitten i batchfilerna så att de kan ändras så att de körs i lämplig konfiguration.

Skapa klientcertifikatet

Följande rader från Setup.bat batchfil skapar det klientcertifikat som ska användas. Variabeln %CLIENT_NAME% anger ämnet för klientcertifikatet. I det här exemplet används "client.com" som ämnesnamn.

Certifikatet lagras i Min (Personlig) butik under lagringsplatsen CurrentUser.

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

Installera klientcertifikatet i serverns betrodda lagringsplats

Följande rad i Setup.bat batchfil kopierar klientcertifikatet till serverns betrodda personarkiv. Det här steget krävs eftersom certifikat som genereras av Makecert.exe inte är implicit betrodda av serverns system. Om du redan har ett certifikat som är rotat i ett klientbetrott rotcertifikat, till exempel ett Microsoft-utfärdat certifikat, krävs inte det här steget för att fylla i klientcertifikatarkivet med servercertifikatet.

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

Skapa servercertifikatet

Följande rader från Setup.bat batchfil skapar det servercertifikat som ska användas. Variabeln %SERVER_NAME% anger servernamnet. Ändra den här variabeln för att ange ditt eget servernamn. Standardvärdet i den här batchfilen är localhost.

Certifikatet lagras i mitt (personligt) arkiv under platsen för LocalMachine-arkivet. Certifikatet lagras i LocalMachine-lagret för IIS-värdtjänster. För tjänster med egen värd bör du ändra batchfilen så att servercertifikatet lagras på plats för CurrentUser-arkivet genom att ersätta strängen LocalMachine med 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

Installera servercertifikat i klientens betrodda certifikatarkiv

Följande rader i Setup.bat batchfil kopierar servercertifikatet till klientens betrodda personers lagring. Det här steget krävs eftersom certifikat som genereras av Makecert.exe inte är implicit betrodda av klientsystemet. Om du redan har ett certifikat som är rotat i ett klientbetrott rotcertifikat, till exempel ett Microsoft-utfärdat certifikat, krävs inte det här steget för att fylla i klientcertifikatarkivet med servercertifikatet.

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

Aktivera åtkomst till certifikatets privata nyckel

Om du vill aktivera åtkomst till certifikatets privata nyckel från den IIS-värdbaserade tjänsten måste användarkontot som IIS-värdbaserade processen körs under beviljas lämpliga behörigheter för den privata nyckeln. Detta görs genom de sista stegen i skriptet 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
Så här konfigurerar du, skapar och kör exemplet
  1. Kontrollera att du har utfört One-Time installationsproceduren för Windows Communication Foundation-exempel.

  2. Skapa lösningen genom att följa anvisningarna i Skapa Windows Communication Foundation-exempel.

  3. Om du vill köra exemplet i en konfiguration med en eller flera datorer använder du följande instruktioner.

Så här kör du exemplet på samma dator
  1. Kör Setup.bat från exempelinstallationsmappen i en Visual Studio-kommandotolk som körs med administratörsbehörighet. Detta installerar alla certifikat som krävs för att köra exemplet.

    Anmärkning

    Den Setup.bat batchfilen är utformad för att köras från en Visual Studio-kommandotolk. Variabeln PATH-miljö som angetts i Visual Studio-kommandotolken pekar på katalogen som innehåller körbara filer som krävs av Setup.bat skriptet. Se till att ta bort certifikaten genom att köra Cleanup.bat när du är klar med exemplet. Andra säkerhetsexempel använder samma certifikat.

  2. Starta Client.exe från \client\bin. Klientaktiviteten visas i klientkonsolprogrammet.

  3. Om klienten och tjänsten inte kan kommunicera kan du läsa Felsökningstips för WCF-exempel.

Så här kör du exemplet mellan datorer
  1. Skapa en katalog på tjänstdatorn. Skapa ett virtuellt program med namnet servicemodelsamples för den här katalogen med hjälp av IIS-hanteringsverktyget (Internet Information Services).

  2. Kopiera tjänstprogramfilerna från \inetpub\wwwroot\servicemodelsamples till den virtuella katalogen på tjänstdatorn. Se till att du kopierar filerna i underkatalogen \bin. Kopiera även Setup.bat, Cleanup.batoch ImportClientCert.bat filer till tjänstdatorn.

  3. Skapa en katalog på klientdatorn för klient binärfilerna.

  4. Kopiera klientprogramfilerna till klientkatalogen på klientdatorn. Kopiera även Setup.bat, Cleanup.batoch ImportServiceCert.bat filer till klienten.

  5. På servern kör du setup.bat service i en kommandotolk för utvecklare för Visual Studio som öppnats med administratörsbehörighet. Att köra setup.bat med service argumentet skapar ett tjänstcertifikat med den fullständigt kvalificerade domännamnet för datorn och exporterar tjänstcertifikatet till en fil med namnet Service.cer.

  6. Redigera Web.config för att återspegla det nya certifikatnamnet (i findValueattributet i< serviceCertificate>) som är samma som datorns fullständigt kvalificerade domännamn.

  7. Kopiera Service.cer-filen från tjänstkatalogen till klientkatalogen på klientdatorn.

  8. På klienten kör du setup.bat client i en kommandotolk för utvecklare för Visual Studio som öppnats med administratörsbehörighet. När du kör setup.bat med argumentet client skapas ett klientcertifikat med namnet client.com och klientcertifikatet exporteras till en fil med namnet Client.cer.

  9. I filen Client.exe.config på klientdatorn ändrar du slutpunktens adressvärde så att det matchar tjänstens nya adress. Gör detta genom att ersätta localhost med serverns fullständigt kvalificerade domännamn.

  10. Kopiera Client.cer-filen från klientkatalogen till tjänstkatalogen på servern.

  11. Kör ImportServiceCert.batpå klienten . Detta importerar tjänstcertifikatet från filen Service.cer till lagringsplatsen CurrentUser – TrustedPeople.

  12. På servern kör du ImportClientCert.bat, detta importerar klientcertifikatet från Client.cer-filen till LocalMachine – TrustedPeople-arkivet.

  13. Starta Client.exe från ett kommandopromptfönster på klientmaskinen. Om klienten och tjänsten inte kan kommunicera kan du läsa Felsökningstips för WCF-exempel.

Att städa upp efter provet
  • Kör Cleanup.bat i exempelmappen när du har kört exemplet.

Anmärkning

Det här skriptet tar inte bort tjänstcertifikat på en klient när du kör det här exemplet på flera datorer. Om du har kört WCF-exempel som använder certifikat mellan datorer, bör du rensa de tjänstcertifikat som har installerats i lagret CurrentUser – TrustedPeople. Det gör du genom att använda följande kommando: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Till exempel: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.