Delen via


Ondersteunende tokens

In het voorbeeld Ondersteunende tokens ziet u hoe u extra tokens toevoegt aan een bericht dat WS-Security gebruikt. In het voorbeeld wordt een binair X.509-beveiligingstoken toegevoegd naast een token voor de beveiliging van de gebruikersnaam. Het token wordt doorgegeven in een WS-Security-berichtkop van de client naar de service en een deel van het bericht wordt ondertekend met de persoonlijke sleutel die is gekoppeld aan het X.509-beveiligingstoken om het bezit van het X.509-certificaat aan de ontvanger te bewijzen. Dit is handig in het geval dat er meerdere claims moeten zijn gekoppeld aan een bericht om de afzender te verifiëren of te autoriseren. De service implementeert een contract dat een communicatiepatroon aanvraagantwoord definieert.

Demonstreert

In het voorbeeld ziet u het volgende:

  • Hoe een client aanvullende beveiligingstokens aan een service kan doorgeven.

  • Hoe de server toegang heeft tot claims die zijn gekoppeld aan aanvullende beveiligingstokens.

  • Hoe het X.509-certificaat van de server wordt gebruikt om de symmetrische sleutel te beveiligen die wordt gebruikt voor berichtversleuteling en handtekening.

Notitie

De installatieprocedure en build-instructies voor dit voorbeeld bevinden zich aan het einde van dit onderwerp.

Client wordt geverifieerd met een gebruikersnaamtoken en het ondersteunende X.509-beveiligingstoken

De service maakt één eindpunt beschikbaar voor communicatie die programmatisch is gemaakt met behulp van de BindingHelper en EchoServiceHost klassen. Het eindpunt bestaat uit een adres, een binding en een contract. De binding wordt geconfigureerd met een aangepaste binding met behulp van SymmetricSecurityBindingElement en HttpTransportBindingElement. In dit voorbeeld wordt het SymmetricSecurityBindingElement gebruik van een X.509-servicecertificaat ingesteld voor het beveiligen van de symmetrische sleutel tijdens de verzending en het doorgeven van een UserNameToken ondersteuning X509SecurityToken in een WS-Security-berichtkop. De symmetrische sleutel wordt gebruikt om de hoofdtekst van het bericht en het beveiligingstoken voor de gebruikersnaam te versleutelen. Het ondersteunende token wordt doorgegeven als een extra binair beveiligingstoken in de berichtkop WS-Security. De echtheid van het ondersteunende token wordt bewezen door een deel van het bericht te ondertekenen met de persoonlijke sleutel die is gekoppeld aan het ondersteunende X.509-beveiligingstoken.

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

Het gedrag geeft de servicereferenties op die moeten worden gebruikt voor clientverificatie en ook informatie over het X.509-certificaat van de service. Het voorbeeld gebruikt CN=localhost als onderwerpnaam in het X.509-certificaat van de service.

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

Servicecode:

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

Het clienteindpunt wordt op een vergelijkbare manier geconfigureerd als het service-eindpunt. De client gebruikt dezelfde BindingHelper klasse om een binding te maken. De rest van de installatie bevindt zich in Client klasse. De client stelt informatie in over het beveiligingstoken van de gebruikersnaam, het ondersteunende X.509-beveiligingstoken en informatie over het X.509-certificaat van de service in de installatiecode voor het gedrag van het clienteindpunt.

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

Informatie van bellers weergeven

Als u de gegevens van de beller wilt weergeven, kunt u de ServiceSecurityContext.Current.AuthorizationContext.ClaimSets informatie gebruiken zoals wordt weergegeven in de volgende code. De ServiceSecurityContext.Current.AuthorizationContext.ClaimSets bevat autorisatieclaims die zijn gekoppeld aan de huidige aanroeper. Deze claims worden automatisch door Windows Communication Foundation (WCF) verstrekt voor elk token dat in het bericht wordt ontvangen.

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

Het voorbeeld uitvoeren

Wanneer u het voorbeeld uitvoert, wordt u door de client eerst gevraagd om de gebruikersnaam en het wachtwoord voor het token voor de gebruikersnaam op te geven. Zorg ervoor dat u de juiste waarden voor uw systeemaccount opgeeft, omdat WCF in de service de waarden die zijn opgegeven in het token voor de gebruikersnaam toe te voegen aan de identiteit die door het systeem wordt geleverd. Daarna geeft de client het antwoord van de service weer. Druk op Enter in het clientvenster om de client af te sluiten.

Batch-bestand instellen

Met het Setup.bat batchbestand dat bij dit voorbeeld is opgenomen, kunt u de server configureren met relevante certificaten om door Internet Information Services (IIS) gehoste toepassing uit te voeren waarvoor beveiliging op basis van servercertificaten is vereist. Dit batchbestand moet worden gewijzigd om te kunnen werken op alle computers of om te kunnen werken in een niet-gehost geval.

Hieronder vindt u een kort overzicht van de verschillende secties van de batchbestanden, zodat ze kunnen worden gewijzigd om in de juiste configuratie te worden uitgevoerd.

Het clientcertificaat maken

Met de volgende regels uit het Setup.bat batchbestand maakt u het clientcertificaat dat moet worden gebruikt. De %CLIENT_NAME% variabele geeft het onderwerp van het clientcertificaat op. In dit voorbeeld wordt 'client.com' gebruikt als onderwerpnaam.

Het certificaat wordt opgeslagen in mijn (persoonlijke) archief onder de CurrentUser winkellocatie.

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

Het clientcertificaat installeren in het vertrouwde archief van de server

Met de volgende regel in het Setup.bat batchbestand wordt het clientcertificaat gekopieerd naar het archief vertrouwde personen van de server. Deze stap is vereist omdat certificaten die worden gegenereerd door Makecert.exe niet impliciet worden vertrouwd door het systeem van de server. Als u al een certificaat hebt dat is geroot in een vertrouwd basiscertificaat van een client, bijvoorbeeld een door Microsoft uitgegeven certificaat, is deze stap van het vullen van het clientcertificaatarchief met het servercertificaat niet vereist.

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

Het servercertificaat maken

De volgende regels uit het Setup.bat batchbestand maken het servercertificaat dat moet worden gebruikt. De %SERVER_NAME% variabele geeft de servernaam op. Wijzig deze variabele om uw eigen servernaam op te geven. De standaardinstelling in dit batchbestand is localhost.

Het certificaat wordt opgeslagen in Mijn (Persoonlijk) archief onder de locatie van het LocalMachine-archief. Het certificaat wordt opgeslagen in het LocalMachine-archief voor de iis-gehoste services. Voor zelf-hostende services moet u het batchbestand wijzigen om het servercertificaat op te slaan in de CurrentUser-opslaglocatie door de tekenreeks LocalMachine te vervangen door 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

Servercertificaat installeren in het vertrouwde certificaatarchief van de client

Met de volgende regels in het Setup.bat batchbestand kopieert u het servercertificaat naar het archief vertrouwde personen van de client. Deze stap is vereist omdat certificaten die worden gegenereerd door Makecert.exe niet impliciet worden vertrouwd door het clientsysteem. Als u al een certificaat hebt dat is geroot in een vertrouwd basiscertificaat van een client, bijvoorbeeld een door Microsoft uitgegeven certificaat, is deze stap van het vullen van het clientcertificaatarchief met het servercertificaat niet vereist.

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

Toegang tot de persoonlijke sleutel van het certificaat inschakelen

Als u toegang tot de persoonlijke sleutel van het certificaat wilt inschakelen vanuit de door IIS gehoste service, moet aan het gebruikersaccount waarop het iis-hostproces wordt uitgevoerd, de juiste machtigingen worden verleend voor de persoonlijke sleutel. Dit wordt bereikt door de laatste stappen in het Setup.bat script.

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
Het voorbeeld instellen, compileren en uitvoeren
  1. Zorg ervoor dat u de eenmalige installatieprocedure voor de Windows Communication Foundation-voorbeelden hebt uitgevoerd.

  2. Volg de instructies in Het bouwen van de Windows Communication Foundation-voorbeelden om de oplossing te bouwen.

  3. Gebruik de volgende instructies om het voorbeeld uit te voeren in een configuratie van één of meerdere computers.

Het voorbeeld uitvoeren op dezelfde computer
  1. Voer Setup.bat uit vanuit de voorbeeldinstallatiemap in een Visual Studio-opdrachtprompt die wordt uitgevoerd met beheerdersbevoegdheden. Hiermee worden alle certificaten geïnstalleerd die vereist zijn voor het uitvoeren van het voorbeeld.

    Notitie

    Het Setup.bat batchbestand is ontworpen om te worden uitgevoerd vanaf een Visual Studio-opdrachtprompt. De omgevingsvariabele PATH die in de Visual Studio-opdrachtprompt is ingesteld, verwijst naar de map met uitvoerbare bestanden die zijn vereist voor het Setup.bat script. Verwijder de certificaten door Cleanup.bat uit te voeren wanneer u klaar bent met het voorbeeld. Andere beveiligingsvoorbeelden gebruiken dezelfde certificaten.

  2. Start Client.exe vanuit \client\bin. Clientactiviteit wordt weergegeven in de clientconsoletoepassing.

  3. Als de client en service niet kunnen communiceren, raadpleegt u Tips voor probleemoplossing voor WCF-voorbeelden.

Het voorbeeld uitvoeren op verschillende computers
  1. Maak een map op de servicecomputer. Maak een virtuele toepassing met de naam servicemodelsamples voor deze map met behulp van het iis-beheerprogramma (Internet Information Services).

  2. Kopieer de serviceprogrammabestanden van \inetpub\wwwroot\servicemodelsamples naar de virtuele map op de servicemachine. Zorg ervoor dat u de bestanden in de submap \bin kopieert. Kopieer ook de bestanden Setup.bat, Cleanup.bat en ImportClientCert.bat naar de servicemachine.

  3. Maak een map op de clientcomputer voor de binaire clientbestanden.

  4. Kopieer de clientprogrammabestanden naar de clientmap op de clientcomputer. Kopieer ook de bestanden Setup.bat, Cleanup.bat en ImportServiceCert.bat naar de client.

  5. Voer setup.bat service op de server uit in een opdrachtprompt voor ontwikkelaars voor Visual Studio die is geopend met beheerdersbevoegdheden. Als u het service argument uitvoertsetup.bat, maakt u een servicecertificaat met de volledig gekwalificeerde domeinnaam van de computer en exporteert u het servicecertificaat naar een bestand met de naam Service.cer.

  6. Bewerk Web.config om de nieuwe certificaatnaam (in het findValue kenmerk in serviceCertificate <) weer te> geven. Dit is hetzelfde als de volledig gekwalificeerde domeinnaam van de computer.

  7. Kopieer het Service.cer-bestand van de servicemap naar de clientmap op de clientcomputer.

  8. Voer setup.bat client op de client uit in een opdrachtprompt voor ontwikkelaars voor Visual Studio die is geopend met beheerdersbevoegdheden. Als u het argument uitvoert setup.bat , maakt u een clientcertificaat met de client naam client.com en exporteert u het clientcertificaat naar een bestand met de naam Client.cer.

  9. Wijzig in het bestand Client.exe.config op de clientcomputer de adreswaarde van het eindpunt zodat deze overeenkomt met het nieuwe adres van uw service. Doe dit door localhost te vervangen door de volledig gekwalificeerde domeinnaam van de server.

  10. Kopieer het Client.cer-bestand van de clientmap naar de servicemap op de server.

  11. Voer op de client ImportServiceCert.bat uit. Hiermee importeert u het servicecertificaat uit het Service.cer-bestand in het CurrentUser - Trusted Mensen-archief.

  12. Voer op de server ImportClientCert.bat uit. Hiermee importeert u het clientcertificaat uit het Client.cer-bestand in het archief LocalMachine - Trusted Mensen.

  13. Start op de clientcomputer Client.exe vanuit een opdrachtpromptvenster. Als de client en service niet kunnen communiceren, raadpleegt u Tips voor probleemoplossing voor WCF-voorbeelden.

Opschonen na het voorbeeld
  • Voer Cleanup.bat uit in de map met voorbeelden zodra u klaar bent met het uitvoeren van het voorbeeld.

Notitie

Dit script verwijdert geen servicecertificaten op een client bij het uitvoeren van dit voorbeeld op computers. Als u WCF-voorbeelden hebt uitgevoerd die gebruikmaken van certificaten op computers, moet u de servicecertificaten wissen die zijn geïnstalleerd in het CurrentUser - Trusted Mensen-archief. Gebruik hiervoor de volgende opdracht: bijvoorbeeld: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name>certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.