Sdílet prostřednictvím


Podpůrné tokeny

Ukázka podpůrných tokenů ukazuje, jak přidat další tokeny do zprávy, která používá WS-Security. Příklad přidá kromě tokenu zabezpečení uživatelského jména také binární token zabezpečení X.509. Token se předá v hlavičce WS-Security zprávy od klienta do služby a část zprávy je podepsána privátním klíčem přidruženým k tokenu zabezpečení X.509, aby se potvrdilo vlastnictví certifikátu X.509 příjemci. To je užitečné v případě, že je potřeba mít více deklarací identity spojené se zprávou k ověření nebo autorizaci odesílatele. Služba implementuje kontrakt, který definuje komunikační vzor žádosti a odpovědi.

Demonstruje

Ukázka ukazuje:

  • Jak může klient předat službě další tokeny zabezpečení.

  • Jak může server získat přístup k deklaracím identity přidruženým k dalším tokenům zabezpečení.

  • Jak se certifikát X.509 serveru používá k ochraně symetrického klíče používaného k šifrování zpráv a podpisu.

Poznámka:

Postup nastavení a pokyny k sestavení pro tuto ukázku najdete na konci tohoto tématu.

Klient se ověřuje pomocí tokenu uživatelského jména a podpůrného tokenu zabezpečení X.509

Služba zveřejňuje jeden koncový bod pro komunikaci, který je vytvořený prostřednictvím kódu programu pomocí tříd BindingHelper a EchoServiceHost. Koncový bod se skládá z adresy, vazby a kontraktu. Vazba je nakonfigurována s vlastní vazbou pomocí SymmetricSecurityBindingElement a HttpTransportBindingElement. Tato ukázka nastaví SymmetricSecurityBindingElement, aby použila certifikát X.509 služby k ochraně symetrického klíče během přenosu, a zahrne UserNameToken spolu s podporou X509SecurityToken do hlavičky zprávy WS-Security. Symetrický klíč slouží k šifrování textu zprávy a tokenu zabezpečení uživatelského jména. Podpůrný token se předá jako další binární token zabezpečení v hlavičce WS-Security zprávy. Pravost podpůrného tokenu se prokáže podepsáním části zprávy pomocí privátního klíče přidruženého k podpůrnému tokenu zabezpečení X.509.

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

Chování určuje přihlašovací údaje služby, které se mají použít pro ověřování klientů, a také informace o certifikátu X.509 služby. Ukázka se používá CN=localhost jako název subjektu v certifikátu X.509 služby.

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

Kód služby:

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

Koncový bod klienta se konfiguruje podobným způsobem jako koncový bod služby. Klient používá stejnou BindingHelper třídu k vytvoření vazby. Zbytek nastavení se nachází ve Client třídě. Klient nastaví informace o tokenu zabezpečení uživatelského jména, podpůrném tokenu zabezpečení X.509 a informace o certifikátu služby X.509 v instalačním kódu na kolekci chování koncových bodů klienta.

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

Zobrazení informací o volajících

Pokud chcete zobrazit informace volajícího, můžete použít ServiceSecurityContext.Current.AuthorizationContext.ClaimSets kód, jak je znázorněno v následujícím kódu. ServiceSecurityContext.Current.AuthorizationContext.ClaimSets obsahuje autorizace přidružené k aktuálnímu volajícímu. Tyto deklarace identity jsou automaticky poskytovány službou Windows Communication Foundation (WCF) pro každý token přijatý ve zprávě.

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

Spustit ukázkový kód

Když spustíte ukázku, klient vás nejprve vyzve k zadání uživatelského jména a hesla tokenu uživatelského jména. Nezapomeňte zadat správné hodnoty pro váš systémový účet, protože WCF ve službě mapuje hodnoty zadané v tokenu uživatelského jména na identitu poskytovanou systémem. Potom klient zobrazí odpověď ze služby. Stisknutím klávesy ENTER v okně klienta klienta ukončete klienta.

Nastavení dávkového souboru

Dávkový soubor Setup.bat, který je součástí této ukázky, umožňuje nakonfigurovat server s příslušnými certifikáty pro spuštění aplikace hostované internetovou informační službou (IIS), která vyžaduje zabezpečení založené na certifikátech serveru. Tento dávkový soubor musí být upraven tak, aby fungoval na počítačích nebo v nehostovaném prostředí.

Následuje stručný přehled různých částí dávkových souborů, aby je bylo možné upravit tak, aby běžely v příslušné konfiguraci.

Vytvoření klientského certifikátu

Následující řádky z dávkového souboru Setup.bat vytvoří klientský certifikát, který se má použít. Proměnná %CLIENT_NAME% určuje předmět klientského certifikátu. Tato ukázka používá jako název subjektu "client.com".

Certifikát je uložen v úložišti Moje (Osobní) v umístění úložiště CurrentUser.

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

Instalace klientského certifikátu do důvěryhodného úložiště serveru

Následující řádek v dávkovém souboru Setup.bat zkopíruje klientský certifikát do úložiště důvěryhodných osob serveru. Tento krok je povinný, protože certifikáty vygenerované Makecert.exe nejsou systémem serveru implicitně důvěryhodné. Pokud už máte certifikát, který je kořenový v kořenovém certifikátu klienta ( například certifikát vydaný Microsoftem), tento krok naplnění úložiště klientských certifikátů certifikátem pomocí certifikátu serveru se nevyžaduje.

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

Vytvoření certifikátu serveru

Následující řádky z dávkového souboru Setup.bat vytvoří certifikát serveru, který se má použít. Proměnná %SERVER_NAME% určuje název serveru. Změňte tuto proměnnou tak, aby byla zadána vlastní název serveru. Výchozí hodnota v tomto dávkovém souboru je localhost.

Certifikát je uložený v úložišti My (Personal) pod umístěním úložiště LocalMachine. Certifikát je uložen v úložišti LocalMachine pro služby hostované službou IIS. Pro hostované služby byste měli upravit dávkový soubor tak, aby se certifikát serveru ukládal do umístění úložiště CurrentUser nahrazením řetězce LocalMachine řetězcem 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

Instalace certifikátu serveru do důvěryhodného úložiště certifikátů klienta

Následující řádky v dávkovém souboru Setup.bat zkopírují certifikát serveru do úložiště důvěryhodných lidí klienta. Tento krok je povinný, protože certifikáty generované Makecert.exe nejsou implicitně důvěryhodné klientským systémem. Pokud už máte certifikát, který je kořenový v kořenovém certifikátu klienta ( například certifikát vydaný Microsoftem), tento krok naplnění úložiště klientských certifikátů certifikátem pomocí certifikátu serveru se nevyžaduje.

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

Povolení přístupu k privátnímu klíči certifikátu

Pokud chcete povolit přístup k privátnímu klíči certifikátu ze služby hostované službou IIS, musí mít uživatelský účet, pod kterým je spuštěný proces hostované službou IIS, udělena příslušná oprávnění pro privátní klíč. Toho dosáhnete posledním postupem ve skriptu 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
Jak nastavit, sestavit a spustit ukázku
  1. Ujistěte se, že jste provedli One-Time postup nastavení ukázek Windows Communication Foundation.

  2. Pro sestavení řešení postupujte podle pokynů v Sestavení ukázek Windows Communication Foundation.

  3. Pokud chcete spustit ukázku v konfiguraci jednoho nebo více počítačů, postupujte podle následujících pokynů.

Pro spuštění ukázky na stejném počítači
  1. Spusťte Setup.bat z ukázkové instalační složky v příkazovém řádku sady Visual Studio s oprávněními správce. Tím se nainstalují všechny certifikáty potřebné pro spuštění ukázky.

    Poznámka:

    Dávkový soubor Setup.bat je navržený tak, aby běžel z příkazového řádku sady Visual Studio. Proměnná prostředí PATH nastavená v příkazovém řádku sady Visual Studio odkazuje na adresář, který obsahuje spustitelné soubory vyžadované skriptem Setup.bat. Po dokončení ukázky nezapomeňte odebrat certifikáty spuštěním Cleanup.bat. Jiné ukázky zabezpečení používají stejné certifikáty.

  2. Spusťte Client.exe z \client\bin. Aktivita klienta se zobrazí v aplikaci konzoly klienta.

  3. Pokud klient a služba nemůžou komunikovat, přečtěte si Tipy pro řešení potíží v ukázkách WCF.

Spuštění ukázky na různých počítačích
  1. Vytvořte adresář na počítači služby. Pomocí nástroje pro správu Internetové informační služby (IIS) vytvořte virtuální aplikaci s názvem servicemodelsamples pro tento adresář.

  2. Zkopírujte soubory programu služby z \inetpub\wwwroot\servicemodelsamples do virtuálního adresáře na počítači služby. Ujistěte se, že kopírujete soubory v podadresáři \bin. Zkopírujte také soubory Setup.bat, Cleanup.bata ImportClientCert.bat do počítače služby.

  3. Vytvořte adresář na klientském počítači pro binární soubory klienta.

  4. Zkopírujte soubory klientského programu do klientského adresáře na klientském počítači. Zkopírujte také soubory Setup.bat, Cleanup.bata ImportServiceCert.bat do klienta.

  5. Na serveru spusťte setup.bat service příkazový řádek pro vývojáře pro Visual Studio otevřený s oprávněními správce. Spuštění setup.bat s argumentem service vytvoří certifikát služby s plně kvalifikovaným názvem domény počítače a exportuje certifikát služby do souboru s názvem Service.cer.

  6. Upravte Web.config tak, aby odrážel nový název certifikátu (v findValue atributu <v serviceCertificate>), který je stejný jako plně kvalifikovaný název domény počítače.

  7. Zkopírujte soubor Service.cer z adresáře služby do klientského adresáře na klientském počítači.

  8. Na klientovi spusťte setup.bat client příkazový řádek pro vývojáře pro Visual Studio otevřený s oprávněními správce. Spuštění setup.bat s argumentem client vytvoří klientský certifikát s názvem client.com a exportuje klientský certifikát do souboru s názvem Client.cer.

  9. V souboru Client.exe.config na klientském počítači změňte hodnotu adresy koncového bodu tak, aby odpovídala nové adrese vaší služby. Provedete to tak, že nahradíte localhost plně kvalifikovaným názvem domény serveru.

  10. Zkopírujte soubor Client.cer z klientského adresáře do adresáře služby na serveru.

  11. Na klientovi spusťte ImportServiceCert.bat. Tím se certifikát služby naimportuje ze souboru Service.cer do úložiště CurrentUser – TrustedPeople.

  12. Na serveru spusťte ImportClientCert.bat. Tím se naimportuje klientský certifikát ze souboru Client.cer do úložiště LocalMachine – TrustedPeople.

  13. Na klientském počítači spusťte Client.exe z okna příkazového řádku. Pokud klient a služba nemůžou komunikovat, přečtěte si Tipy pro řešení potíží v ukázkách WCF.

Úklid po vzorku
  • Po dokončení spuštění ukázky spusťte Cleanup.bat ve složce s ukázkami.

Poznámka:

Tento skript neodebere certifikáty služeb na klientském zařízení při spuštění této ukázky na více zařízeních. Pokud jste spustili ukázky WCF, které používají certifikáty napříč počítači, nezapomeňte vymazat certifikáty služeb nainstalované v úložišti CurrentUser – TrustedPeople. K tomu použijte následující příkaz: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Například: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.