Самостоятельная служба WCF REST и обычная аутентификация
Я создал самообслуживаемую службу WCF REST (с некоторыми дополнительными средствами из WCF REST Starter Kit Preview 2). Это все работает нормально.
Теперь я пытаюсь добавить базовую проверку подлинности в службу. Но я попадаю в довольно большие блокпосты в стеке WCF, который мешает мне это делать.
Похоже, что HttpListener
(которые самообслуживаемые службы WCF используют внутри себя на низком уровне в стеке WCF) блокирует мои попытки вставить заголовок WWW-Authenticate
в самогенерируемый ответ 401 Unauthorized
. Почему?
Я могу заставить аутентификацию работать, если я забуду об этом заголовке WWW-Authenticate
(что, похоже, и Microsoft). Но это вопрос. Если я не отправлю назад заголовок WWW-Authenticate
, тогда веб-браузер не отобразит свой стандартный диалог входа в систему. Пользователь просто столкнется с страницей ошибок 401 Unauthorized
без возможности входа в систему.
Услуги REST должны быть доступны как для компьютеров, так и для людей (по крайней мере, на уровне запроса GET). Поэтому я чувствую, что WCF REST не выполняет основную часть REST здесь. Кто-нибудь согласен со мной?
Кто-нибудь получил обычную проверку подлинности с помощью автономной службы WCF REST? Если да, то как вы это сделали?
PS: Очевидно, что мои намерения использовать небезопасную обычную проверку подлинности основаны на том, что я также получаю HTTPS/SSL для моего сервиса. Но это другое дело.
PPS: я пробовал WCF REST Contrib (http://wcfrestcontrib.codeplex.com/), и это имеет точно такую же проблему. Похоже, что эта библиотека не была протестирована в сценариях самостоятельно.
Спасибо.
Ответы
Ответ 1
К сожалению, я определил (проанализировав исходный код ссылки WCF и помощь инструмента Fiddler для нюхания сеанса HTTP), что это ошибка в стеке WCF.
Используя Fiddler, я заметил, что моя служба WCF вела себя в отличие от любого другого веб-сайта, который использует обычную проверку подлинности.
Чтобы быть ясным, это то, что ДОЛЖНО произойти:
- Браузер отправляет запрос
GET
без знания того, что пароль даже необходим.
- Веб-сервер отклоняет запрос с состоянием
401 Unauthorized
и включает заголовок WWW-Authenticate
, содержащий информацию о допустимых методах проверки подлинности.
- Браузер предлагает пользователю ввести учетные данные.
- Браузер отправляет запрос
GET
и включает соответствующий заголовок Authentication
с учетными данными.
- Если учетные данные были верны, веб-сервер отвечает
200 OK
и веб-страницей.
Если учетные данные были неправильными, веб-сервер отвечает 401 Unauthorized
и включает в себя тот же заголовок WWW-Authenticate
, который он сделал на шаге 2.
Что было в действительности с моей службой WCF, было следующее:
- Браузер отправляет запрос
GET
без знания того, что пароль даже необходим.
- WCF отмечает, что в запросе нет заголовка
Authentication
и слепо отклоняет запрос с состоянием 401 Unauthorized
и включает заголовок WWW-Authenticate
. Все нормально до сих пор.
- Браузер запрашивает у пользователя учетные данные. Все еще нормально.
- Браузер отправляет запрос
GET
, включая соответствующий заголовок Authentication
.
- Если учетные данные были правильными, веб-сервер отвечает
200 OK
. Все хорошо.
Если учетные данные были неверными, WCF отвечает 403 Forbidden
и не включает никаких дополнительных заголовков, таких как WWW-Authenticate
.
Когда браузер получает статус 403 Forbidden
, он не воспринимает это как неудачную попытку аутентификации. Этот код состояния предназначен для информирования браузера о том, что URL, к которому он пытался получить доступ, не работает. Это никак не связано с аутентификацией. Это страшная сторона влияет на то, что, когда пользователь неправильно вводит свое имя пользователя/пароль (а сервер отклоняется с помощью 403), веб-браузер не перепрофилирует пользователя для повторного ввода своих учетных данных. Фактически, веб-браузер считает, что аутентификация прошла успешно и поэтому сохраняет эти учетные данные для остальной части сеанса!
С учетом этого я просил разъяснений:
RFC 2617 (http://www.faqs.org/rfcs/rfc2617.html#ixzz0eboUfnrl) нигде не упоминает использование кода состояния 403 Forbidden
. Фактически, то, что он на самом деле должен сказать по этому вопросу, следующее:
Если исходный сервер не желает принять учетные данные, отправленные с помощью запрос, он ДОЛЖЕН возвратить 401 (Неавторизованный) ответ. Ответ ДОЛЖЕН включать заголовок WWW-Authenticate поле, содержащее по крайней мере один (возможно, новой), применимой к запрошенный ресурс.
WCF ничего не делает. Он не правильно отправляет код состояния 401 Unauthorized
. Он также не включает заголовок WWW-Authenticate
.
Теперь, чтобы найти курящее пистолет в исходном коде WCF:
Я обнаружил, что в классе HttpRequestContext
есть метод под названием ProcessAuthentication
, который содержит следующее (выдержка):
if (!authenticationSucceeded)
{
SendResponseAndClose(HttpStatusCode.Forbidden);
}
Я защищаю Microsoft от многих вещей, но это необоснованно.
К счастью, у меня это работает на "приемлемом" уровне. Это просто означает, что если пользователь случайно ошибочно вводит свое имя пользователя/пароль, единственный способ получить еще одну попытку - полностью закрыть свой веб-браузер и перезапустить его, чтобы повторить попытку. Все потому, что WCF не отвечает на неудачную попытку аутентификации с заголовками 401 Unauthorized
и WWW-Authenticate
в соответствии со спецификацией.
Ответ 2
Я нашел решение, основанное на этой ссылке и this.
Во-первых, переопределить метод Validate в классе с именем CustomUserNameValidator, который наследует от System.IdentityModel.Selectors.UserNamePasswordValidator:
Imports System.IdentityModel.Selectors
Imports System.ServiceModel
Imports System.Net
Public Class CustomUserNameValidator
Inherits UserNamePasswordValidator
Public Overrides Sub Validate(userName As String, password As String)
If Nothing = userName OrElse Nothing = password Then
Throw New ArgumentNullException()
End If
If Not (userName = "user" AndAlso password = "password") Then
Dim exep As New AddressAccessDeniedException("Incorrect user or password")
exep.Data("HttpStatusCode") = HttpStatusCode.Unauthorized
Throw exep
End If
End Sub
End Class
Трюк изменил атрибут исключения "HttpStatusCode" на HttpStatusCode.Unauthorized
Во-вторых, создайте serviceBehavior в App.config следующим образом:
<behaviors>
<endpointBehaviors>
<behavior name="HttpEnableBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="SimpleServiceBehavior">
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Service.CustomUserNameValidator, Service"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
Наконец, мы должны указать конфигурацию для webHttpBinding и применить ее к конечной точке:
<bindings>
<webHttpBinding>
<binding name="basicAuthBinding">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Basic" realm=""/>
</security>
</binding>
</webHttpBinding>
</bindings>
<service behaviorConfiguration="SimpleServiceBehavior" name="Service.webService">
<host>
<baseAddresses>
<add baseAddress="http://localhost:8000/webService" />
</baseAddresses>
</host>
<endpoint address="http://localhost:8000/webService/restful" binding="webHttpBinding" bindingConfiguration="basicAuthBinding" contract="Service.IwebService" behaviorConfiguration="HttpEnableBehavior"/>
</service>
Ответ 3
Да, вы можете предоставить базовую аутентификацию для служб WCF на основе REST. Однако есть несколько шагов, которые вы должны выполнить, чтобы иметь полное и безопасное решение, и до сих пор большинство ответов являются фрагментами всех необходимых частей.
-
Настройте свою самообслуживаемую службу, чтобы иметь сертификат SSL, привязанный к порту, на котором вы размещаете свою службу WCF. Это очень отличается от применения SSL-сертификата при использовании управляемого хостинга через нечто вроде IIS. Вы должны применить сертификат SSL с помощью утилиты командной строки. Вы НЕ хотите использовать базовую проверку подлинности в службе REST без использования SSL, потому что учетные данные в заголовке небезопасны. Вот (2) подробные сообщения, которые я написал о том, как это сделать. Ваш вопрос слишком велик, чтобы иметь все подробности на форуме, поэтому я предоставляю ссылки с подробной информацией и пошаговыми инструкциями:
Применение и использование SSL-сертификата с самообслуживающей службой WCF
Создание службы RESTful WCF и защита ее использованием HTTPS через SSL
-
Настройте службу для использования обычной проверки подлинности. Это также многокомпонентное решение. 1st настраивает вашу службу для использования обычной проверки подлинности. Во-вторых, создать "customUserNamePasswordValidatorType" и проверить учетные данные для аутентификации клиента. Я вижу, что последнее сообщение не ускользнуло от этого, однако не использовал HTTPS и только 1 очень малая часть решения; будьте осторожны с руководством, которое не обеспечивает комплексного решения, включая конфигурацию и безопасность. Последним шагом является просмотр контекста безопасности для обеспечения авторизации на уровне метода, если это необходимо. В следующей статье, которую я написал, вы поэтапно настраиваете, как настраивать, проверять подлинность и авторизировать своих клиентов.
RESTful Services: проверка подлинности клиентов с использованием базовой проверки подлинности
Это комплексное решение, необходимое для использования Basic Authentication с самообслуживаемыми службами WCF.