Для начала создадим обычный WCF сервис без аутентификации, для этого откроем VisualStudio и добавим в Solution новый проект для WCF сервиса - AuthWCF. В созданный проект добавим интерфейс ISecretService: [ServiceContract] public interface ISecretService { [OperationContract] string GetSecretCode(); } public class SecretService : ISecretService { public string GetSecretCode() { return "password"; } }
Далее реализуем данный интерфейс в WCF сервисе:
Пока ничего сложно, метод GetSecretCode могут вызывать любые пользователи. Но мы хотим, чтобы данный метод могли вызывать только известные нам пользователи, поэтому приступим к реализации процесса аутентификации.
Первое с чем стоит определиться это с Security Mode: Transport, Message или TransportWithMessageCredential. Выбираем тип безопасности на уровне сообщений - Message.
Второе, это видом аутентификации клиента Client Credential Type в зависимости от расположения WCF сервиса(internet, intranet): Windows, Certificate, Digest, Basic, UserName, NTLM, IssuedToken. Выбираем аутентификацию по логину и паролю - UserName, как наиболее универсальный тип.
Определившись со связкой Message/UserName, настроим WCF сервис, добавив в конфигурационный файл (Web.config) строки: <system.serviceModel> <bindings> <wsHttpBinding> <binding name="AuthWCF.SecretServiceBinding"> <!-- Укажем Security Mode --> <security mode="Message"> <!-- Тип аутентификации --> <message clientCredentialType="UserName"/> </security> </binding> </wsHttpBinding> </bindings> <services> <service name="AuthWCF.SecretService" behaviorConfiguration="AuthWCF.SecretServiceBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost/authwcf/"/> </baseAddresses> </host> <endpoint address="" binding="wsHttpBinding" bindingConfiguration="AuthWCF.SecretServiceBinding" contract="AuthWCF.ISecretService"> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service> </services> <behaviors> <serviceBehaviors> <behavior name="AuthWCF.SecretServiceBehavior"> <serviceMetadata httpGetEnabled="true"/> <serviceDebug includeExceptionDetailInFaults="true"/> <serviceCredentials> <!-- Укажем свой собственный UserNameValidator --> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="AuthWCF.CustomUserNameValidator, AuthWCF"/> <!-- Сертификат из хранилища сертификатов на локальном компьютере в разделе Личные с серийным номером 01 --> <serviceCertificate findValue="01" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySerialNumber"/> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> using System.ServiceModel; using System.IdentityModel.Selectors; namespace AuthWCF { public class CustomUserNameValidator : UserNamePasswordValidator { public override void Validate(string userName, string password) { // Доступ разрешен только пользователю "ilya" по паролю "pass" if (!(userName == "ilya" && password == "pass")) { throw new FaultException("Неверный логин или пароль"); } } } }
В настройках мы указали не существующий CustomUserNameValidator, поэтому добавим в проект класс CustomUserNameValidator, являющийся наследником UserNamePasswordValidator, и реализующий процесс аутентификации пользователя:
Итак, мы реализовали WCF сервис со встроенной аутентификацией клиента. Попробуем запустить его, откроем Web-браузер, и введем адрес к WCF сервису (http://localhost/authwcf), но в ответ увидим следующую картину: #FindKey.exe My LocalMachine cacls.exe "C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" /E /G "ASPNET":R
Эта ошибка означает, что WCF сервис не смог найти, указанный в настройках сертификат. Зачем здесь нужен сертификат? Сертификат нужен для того, чтобы сервис мог пройти аутентификацию на стороне клиента. Если мы доверяем сертификату - то мы доверяем и сервису. Мы же не хотим передавать свои credential неизвестно кому и куда. Для этого нашему WCF-сервису необходимо обзавестись собственным сертификатом ("Создаем сертификаты: OpenSSL").
Сертификат создан и добавлен в соответствующее хранилище, но сервис выдает новую ошибку:
Это означает, что сервис не имеет доступа к private-ключам сертификата. Для того чтобы дать доступ, надо выяснить, под каким пользователем запускается сервис (по умолчанию это ASPNET) и в каком файле хранится ключ. Для этого воспользуемся утилитой Find Private Key Tool:
В открывшемся окошке находим наш сертификат (обратите внимание на серийный номер сертификата):
Нажимаем OK, после чего в консоль будет выведено имя файла с ключами, который можно будет найти в папке C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys\. Зная имя файла, разрешаем процессу ASPNET читать данный файл:
Где xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx имя файла с private-ключом.
Серверная сторона настроена, осталось разобраться с клиентской. Создадим новый консольный проект, в котором добавим "Service Reference" на наш WCF-сервис (http://localhost/authwcf/). Файл WCF прокси и файл конфигурации сгенерятся автоматически. После чего, можно произвести вызов сервис методов, предварительно передав ClientCredentials: using (SecretService.SecretServiceClient srv = new SecretService.SecretServiceClient()) { srv.ClientCredentials.UserName.UserName = "ilya"; srv.ClientCredentials.UserName.Password = "pass"; Console.WriteLine(srv.GetSecretCode()); }
Если заглянуть в конфигурационный файл клиента, то вы увидите раздел, который содержит сертификат сервиса, сохраненном в формате Base64: <identity> <certificate encodedValue="AwAAAAEAAAAUAAAAjuVIDNadCpvwDY2t8ZTIqrD4F88gAAAAAQAAADYCAAAwggIyMIIBmwIBATANBgkqhkiG9w0BAQQFADBkMQswCQYDVQQGEwJSVTEPMA0GA1UECBMGUnVzc2lhMQ8wDQYDVQQHEwZNb3Njb3cxEjAQBgNVBAoTCU15Q29tcGFueTELMAkGA1UECxMCQ0ExEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDAzMzAxNTEwMTJaFw0xMTAzMjUxNTEwMTJaMF8xCzAJBgNVBAYTAlJVMQ8wDQYDVQQIEwZSdXNzaWExEjAQBgNVBAoTCU15Q29tcGFueTEXMBUGA1UECxMOSVQgRGVwYXJ0YW1lbnQxEjAQBgNVBAMTCWxvY2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAxDGVBTIZI6tHRaJhBggS0CvCrf2/Gc4AvxBWm8tzhX6LFSwd7CdJ7c9/rc1fDF6P3LAio/E87Vo+a5qWXTI+yx1/UX8OrQSADbDeKdno8mgfc8rHLaAH64CziHIUuO7gMa3WzIX5ZzAQi1hCrpYwI8r+ZXPvJmW46ZjODQz4IfsCAwEAATANBgkqhkiG9w0BAQQFAAOBgQALHEvG2J0BC3YOgxAZyikRfU0AAf5CUmvb4hTZVICvsgxkQUzjD6GdyPi1RvUi8t4G+7EL1yWuiMx+R+AZjrTTBk83uRUOzM+8qcpT1nwK3TEHuaN9PzyLhbT3fQyOtxW2aXoCcVEVqin6hZE129yGkgeLlMZjzrxN0bqpX3QjdA==" /> </identity> <behaviors> <endpointBehaviors> <behavior name="ClientCertificateBehavior"> <clientCredentials> <serviceCertificate> <authentication certificateValidationMode="ChainTrust" trustedStoreLocation="LocalMachine" /> </serviceCertificate> </clientCredentials> </behavior> </endpointBehaviors> </behaviors>
Можно настроить аутентификацию, на основе доверия CA сертификату (важно о отметить, в таком случае, клиент должен доверять CA, издавшему сертификат для нашего сервиса. Эта проблема решается путем добавления CA сертификата в доверенные центры сертификации текущего клиента), для этого надо добавить следующие строки в конфигурационный файл клиента:
Если у Вас возникло исключение ("The X.509 certificate CN=localhost, OU=IT Departament, O=MyCompany, S=Russia, C=RU chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. Функция отзыва не смогла произвести проверку отзыва для сертификата."), то скорее всего Вы не добавили список отозванных сертификатов для CA в хранилище из предыдущей "статьи".
String login = ServiceSecurityContext.Current.PrimaryIdentity.Name;
Спасибо за статью, особено за работу с сертификатами. Работа с ними порой не так очевидна. MS сами говорят что WCF Security одна из наиболее сложных вещей.
ОтветитьУдалитьВажный момент, который хотел бы отметить тут - действительно важен первоночальный выбор Security Mode, поскольку может в дальнейшем сказаться на возможностях WCF, которые можно использовать на сервисе (например, не будет возможности при Message Security использовать Streamed режим).
Спасибо! Очень хорошая статья. Только забыл упомянуть, что:
ОтветитьУдалить1) Надо зарегистрировать сертификат на клиенте
2) FindKey не видит сертификат пока ты его не добавишь в в самозаверенные сертификаты на IIS.
3) Лично я открывал доступ не на "ASPNET", а на пул, который использует мой сайт
Но может это частные случаи конечно))
@Bon
ОтветитьУдалитьДа, надо чтобы клиент доверял корневому сертификату.
Кстати, если Вы используете IIS7, то там настройка безопасности на основе сертификатов упрощена.
Да, я использую IIS7, но особых удобств не заметил, если можно поподробнее..
ОтветитьУдалитьВот здесь можно глянуть:
ОтветитьУдалитьhttp://www.techdays.ru/videos/1331.html
во-первых огромное спасибо за статью!
ОтветитьУдалитьво-вторых не могли бы вы прояснить возникшие у меня вопросы:
1. этот сервис будет доступен только для одного клиента, сертификата?
2. сертификаты СА и клиентов должны быть установлены как на сервере так и у клиентов?
@BeelDx
ОтветитьУдалить1. Нет, для многих (для тех клиентов, которые доверяют CA) В нашем случае на сервер выдан единственый сертификат, подписанный нашим же CA.
2. Да, либо добавить серверный сертификат, либо CA сертификат в список доверенных, но в случае покупки сертификата у авторизованного центра, довавлять сертификаты в списки доверенных не нужно.
1. Вообщем все сделал как тут прописано, только на разных машинах (сервис и клиент). Выводит ошибку при запуске клиента:
ОтветитьУдалитьConsole.WriteLine(srv.GetSecretCode()); - SOAP security negotition with for target failed.
в чем причина данной ошибки и как ее исправить? =)
2. В данном примере тока сервер имеет сертификат, а клиенты "подписываются" (или что там) на него, так?
3. Если второе верно, то можно ли сделать, так сказать: "двухсторонее сертифицирование", чтоб каждый клиент имел личный сертификат и через него аутентифицироваться на сервере, ну и клиенты должны быть "подписанными" на сертификат сервера. Вообщем что-то типа того), мб и где-то и не так выразился, но думаю общий смысл передал)) Как реализовать такую аутентификацию, защиту??
1. Попробуйте задать http://msdn.microsoft.com/ru-ru/library/aa347698.aspx.
ОтветитьУдалить2. Да, только сервер. Клиент должен только ему доверять, либо доверять его CA. Клиент не отдаст свои login/password сервису если не доверяет ему(сертификату).
3. Да, можно сделать двухстороннее(вместо явного указания login/password передавать сертификат, к которому имеется доверие со стороны сервера), я постараюсь написать статью, а пока только http://msdn.microsoft.com/en-us/library/ff648360.aspx.
Спасибо за ссылки, особенно за последнюю :)
ОтветитьУдалитьвот еще нашел по ошибке http://www.gotdotnet.ru/blogs/natale/4273/ тока не помогло ничего((
Думаю тут дело в сертификатах, у них CN=localhost - вот и работают тока на одной машине (сервис и клиент), где-то читал для сайта в сертификат надо прописывать имя сайта (ну или сервера), исправлять не стал) начал сразу с изучения второй ссылки)))
Как всегда оказалось все на много проще)) выше описанная ошибка решилась добавлением на клиенте списка отозванных сертификатов :)
ОтветитьУдалитьОтлично)
ОтветитьУдалитьа возможна аутентификации по паролу, но чтоб сертификаты не использовать?
ОтветитьУдалитьНе пробовал, но предполагаю что да, возможно.
ОтветитьУдалитьМогу лишь посоветовать:
http://stackoverflow.com/questions/379526/wcf-username-without-certificate и http://stackoverflow.com/questions/6584794/wcf-security-username-password-without-certificate
Здравствуйте Сделал все как у вас. Подскажите пожалуйста, с чем может быть связана ошибка: chain building failed. The certificate that was used has a trust chain that cannot be verified. Replace the certificate or change the certificateValidationMode. Нельзя проверить подпись сертификата.?
ОтветитьУдалитьВ самом конце этой статьи я написал, что: "...скорее всего Вы не добавили список отозванных сертификатов для CA в хранилище из предыдущей статьи (http://www.handcode.ru/2010/03/openssl.html)..."
ОтветитьУдалить1. Как сделать тоже самое только без сертификата
ОтветитьУдалить2. Как быть с ролями пользователей?