Политика авторизации

В этом примере показано, как реализовать пользовательскую политику авторизации утверждений, а также связанный с ней пользовательский диспетчер авторизации службы. Это полезно в случаях, когда служба осуществляет проверки доступа на основе утвержденных требований к операциям службы и перед этими проверками предоставляет вызывающей стороне определенные права. В этом примере показан как процесс добавления утверждений, так и процесса проверки доступа к завершенном набору утверждений. Все сообщения приложения между клиентом и сервером подписываются и шифруются. По умолчанию с wsHttpBinding привязкой имя пользователя и пароль, предоставленные клиентом, используются для входа в действительную учетную запись Windows. В этом примере показано, как использовать настраиваемый UserNamePasswordValidator для аутентификации клиента. Кроме того, в этом примере показана проверка подлинности клиента в службе с помощью сертификата X.509. В этом примере показана реализация IAuthorizationPolicy и ServiceAuthorizationManager, которые предоставляют доступ к определенным методам службы для конкретных пользователей. Этот пример основан на имени пользователя безопасности сообщений, но демонстрирует, как выполнить преобразование утверждений до ServiceAuthorizationManager вызова.

Замечание

Процедура установки и инструкции по сборке для этого примера находятся в конце этого раздела.

В этом примере показано, как:

  • Клиент может пройти проверку подлинности с помощью пароля имени пользователя.

  • Клиент может быть аутентифицирован с помощью сертификата X.509.

  • Сервер проверяет учетные данные клиента с помощью пользовательского UsernamePassword валидатора.

  • Сервер проходит проверку подлинности с помощью сертификата X.509 сервера.

  • Сервер может использовать ServiceAuthorizationManager для управления доступом к определенным методам в службе.

  • Как реализовать IAuthorizationPolicy.

Служба предоставляет две конечные точки для взаимодействия со службой, определяемой с помощью файла конфигурации App.config. Каждая конечная точка состоит из адреса, привязки и контракта. Одна привязка настроена с помощью стандартной wsHttpBinding привязки, которая использует WS-Security и проверку подлинности имени пользователя клиента. Другая привязка настроена со стандартной wsHttpBinding привязкой, которая использует проверку подлинности WS-Security и сертификата клиента. <Поведение> указывает, что учетные данные пользователя должны использоваться для проверки подлинности службы. Сертификат сервера должен содержать такое же значение для свойства SubjectName, как значение атрибута findValue в <serviceCertificate>.

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

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

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

</system.serviceModel>

Каждая конфигурация конечной точки клиента состоит из имени конфигурации, абсолютного адреса конечной точки службы, привязки и контракта. Привязка клиента настраивается с соответствующим режимом безопасности, указанным в <режиме безопасности> и clientCredentialType в <сообщении>.

<system.serviceModel>

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

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

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

  </system.serviceModel>

Для конечной точки на основе имени пользователя реализация клиента задает имя пользователя и пароль для использования.

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

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

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

client1.Close();

Для конечной точки на основе сертификатов реализация клиента задает используемый сертификат клиента.

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

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

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

client2.Close();

В этом примере используется пользовательский UserNamePasswordValidator для проверки имен пользователей и паролей. Пример, реализует MyCustomUserNamePasswordValidator, производный от UserNamePasswordValidator. См. документацию по UserNamePasswordValidator для получения дополнительной информации. В целях демонстрации интеграции с компонентом UserNamePasswordValidator, этот пример настраиваемого проверяющего средства реализует метод Validate, который принимает пары имени пользователя и пароля, где имя пользователя совпадает с паролем, как показано в следующем коде.

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

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

После реализации проверяющего элемента в коде службы узел службы должен быть проинформирован об используемом экземпляре проверяющего элемента. Это делается с помощью следующего кода:

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

Вы также можете выполнить то же самое в конфигурации:

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

Windows Communication Foundation (WCF) предоставляет многофункциональную модель на основе утверждений для выполнения проверок доступа. Объект ServiceAuthorizationManager используется для проверки доступа и определения соответствия утверждений, связанных с клиентом, требованиям, необходимым для доступа к методу службы.

В целях демонстрации в этом примере показана реализация ServiceAuthorizationManager, которая реализует метод CheckAccessCore, позволяющий пользователю получать доступ к методам на основе утверждений типа http://example.com/claims/allowedoperation, значение которых — URI действия операции, которую разрешено вызвать.

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

После внедрения пользовательского ServiceAuthorizationManager необходимо сообщить узлу службы, какой ServiceAuthorizationManager следует использовать. Это сделано, как показано в следующем коде.

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

Основной метод реализации IAuthorizationPolicy — это метод Evaluate(EvaluationContext, Object).

public class MyAuthorizationPolicy : IAuthorizationPolicy
{
    string id;

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

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

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

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

В предыдущем коде показано, как Evaluate(EvaluationContext, Object) метод проверяет, что новые утверждения не были добавлены, которые влияют на обработку и добавляют определенные утверждения. Утверждения, которые разрешены, получаются из GetAllowedOpList метода, который реализуется для возврата определенного списка операций, которые пользователь может выполнять. Политика авторизации добавляет утверждения для доступа к определенной операции. Это впоследствии используется ServiceAuthorizationManager для принятия решений о проверке доступа.

После того как будет реализован настраиваемый IAuthorizationPolicy, необходимо сообщить узлу службы о политиках авторизации, которые необходимо использовать.

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

При запуске примера запросы и ответы операции отображаются в окне консоли клиента. Клиент успешно вызывает методы Сложение, Вычитание и Умножение, и получает сообщение "Доступ запрещен" при попытке вызвать метод Деление. Нажмите клавишу ВВОД в окне клиента, чтобы завершить работу клиента.

Настройка пакетного файла

Входящий в состав образца файл Setup.bat позволяет настроить для сервера соответствующие сертификаты, необходимые для выполнения резидентного приложения, которое требует обеспечения безопасности на основе сертификата сервера.

Ниже приведен краткий обзор различных разделов пакетных файлов, чтобы их можно было изменить для выполнения в соответствующей конфигурации:

  • Создание сертификата сервера.

    Следующие строки из пакетного файла Setup.bat создают используемый сертификат сервера. Переменная %SERVER_NAME% указывает имя сервера. Измените эту переменную, чтобы указать собственное имя сервера. Значение по умолчанию — localhost.

    echo ************
    echo Server cert setup starting
    echo %SERVER_NAME%
    echo ************
    echo making server cert
    echo ************
    makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
    
  • Установка сертификата сервера в доверенное хранилище сертификатов клиента.

    Следующие строки в пакетном файле Setup.bat копируют сертификат сервера в хранилище доверенных пользователей клиента. Этот шаг необходим, потому что сертификаты, созданные Makecert.exe, не вызывают автоматически доверия у клиентской системы. Если у вас уже есть сертификат, основанный на доверенном корневом сертификате клиента, например, выданный корпорацией Майкрософт, этот шаг, в котором хранилище сертификатов клиента заполняется серверным сертификатом, не требуется.

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • Создание сертификата клиента.

    Следующие строки из Setup.bat пакетного файла создают сертификат клиента для использования. Переменная %USER_NAME% указывает имя сервера. Это значение устанавливается как "test1", потому что это имя, которое ищет IAuthorizationPolicy. При изменении значения %USER_NAME% необходимо изменить соответствующее значение в методе IAuthorizationPolicy.Evaluate .

    Сертификат хранится в хранилище My (Personal) в расположении хранилища CurrentUser.

    echo ************
    echo making client cert
    echo ************
    makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe
    
  • Установка сертификата клиента в доверенное хранилище сертификатов сервера.

    Следующие строки в пакетном файле Setup.bat копируют сертификат клиента в хранилище доверенных пользователей. Этот шаг необходим, так как сертификаты, созданные Makecert.exe, не являются неявно доверенными серверной системой. Если у вас уже есть сертификат, который входит в цепочку доверенных корневых сертификатов — например, выданный компанией Microsoft — то данный шаг по добавлению клиентского сертификата в хранилище сертификатов сервера не требуется.

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

Для настройки и сборки примера

  1. Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.

  2. Чтобы запустить образец на одном или нескольких компьютерах, следуйте приведенным далее инструкциям.

Замечание

Если вы используете Svcutil.exe для повторного создания конфигурации для этого примера, обязательно измените имя конечной точки в конфигурации клиента, чтобы соответствовать коду клиента.

Запуск примера на том же компьютере

  1. Откройте командную строку разработчика для Visual Studio с правами администратора и запустите Setup.bat из образца папки установки. При этом устанавливаются все сертификаты, необходимые для выполнения примера.

    Замечание

    Пакетный файл Setup.bat предназначен для запуска из командной строки разработчика для Visual Studio. Переменная среды PATH в командной строке разработчика для Visual Studio указывает на каталог, содержащий исполняемые файлы, необходимые скрипту Setup.bat .

  2. Запустите Service.exe из service\bin.

  3. Запустите Client.exe из \client\bin. Действие клиента отображается в клиентском консольном приложении.

Если клиент и служба не могут взаимодействовать, см. рекомендации по устранению неисправностей для примеров WCF.

Для запуска примера на нескольких компьютерах

  1. Создайте каталог на компьютере службы.

  2. Скопируйте файлы программы службы из \service\bin в каталог на компьютере службы. Кроме того, скопируйте Setup.bat, Cleanup.bat, GetComputerName.vbs и ImportClientCert.bat файлы на компьютер службы.

  3. Создайте каталог на клиентском компьютере для двоичных файлов клиента.

  4. Скопируйте файлы клиентской программы в каталог клиента на клиентском компьютере. Кроме того, скопируйте Setup.bat, Cleanup.batи ImportServiceCert.bat файлы в клиент.

  5. На сервере запустите setup.bat service в командной строке Разработчика для Visual Studio, открытой с правами администратора.

    При выполнении setup.bat с аргументом service создается сертификат службы с полным доменным именем компьютера, а затем сертификат службы экспортируется в файл Service.cer.

  6. Измените Service.exe.config , чтобы отразить новое имя сертификата (в findValue атрибуте <в serviceCertificate>), которое совпадает с полным доменным именем компьютера. Кроме того, измените имя компьютера в элементе <service>/<baseAddresses> с localhost на полное имя компьютера службы.

  7. Скопируйте файл Service.cer из каталога службы в клиентский каталог на клиентском компьютере.

  8. На клиенте откройте командную строку разработчика для Visual Studio с правами администратора и запустите setup.bat client.

    При запуске setup.bat с аргументом client создается клиентский сертификат с именем test1 и экспортируется в файл Client.cer.

  9. В файлеClient.exe.config на клиентском компьютере измените значение адреса конечной точки, чтобы он соответствовал новому адресу службы. Для этого замените localhost полным доменным именем сервера.

  10. Скопируйте файл Client.cer из клиентского каталога в каталог службы на сервере.

  11. На клиенте запустите ImportServiceCert.bat в командной строке разработчика для Visual Studio, открывшейся с правами администратора.

    При этом сертификат службы импортируется из файла Service.cer в хранилище CurrentUser — TrustedPeople .

  12. На сервере запустите ImportClientCert.bat в командной строке разработчика для Visual Studio, открывшейся с правами администратора.

    При этом сертификат клиента импортируется из файла Client.cer в хранилище LocalMachine — TrustedPeople .

  13. На серверном компьютере запустите Service.exe из окна командной строки.

  14. На клиентском компьютере запустите Client.exe из окна командной строки.

    Если клиент и служба не могут взаимодействовать, см. рекомендации по устранению неисправностей для примеров WCF.

Убрать после образца

Чтобы очистить после примера, запустите Cleanup.bat в папке примеров после завершения выполнения примера. Это удаляет сертификаты сервера и клиента из хранилища сертификатов.

Замечание

Этот скрипт не удаляет сертификаты службы на клиенте при запуске этого примера на компьютерах. Если вы запускали примеры WCF, использующие сертификаты на разных компьютерах, обязательно удалите сертификаты службы, установленные в хранилище CurrentUser-TrustedPeople. Для этого используйте следующую команду: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Например, certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.