Самостоятельная служба WCF REST и обычная аутентификация
Я создал самостоятельно размещенную службу WCF REST (с некоторыми дополнительными функциями из предварительного просмотра WCF REST Starter Kit 2). Это все работает нормально.
Я сейчас пытаюсь добавить обычную аутентификацию в сервис. Но я сталкиваюсь с некоторыми довольно большими препятствиями в стеке WCF, что мешает мне сделать это.
Похоже, что HttpListener
(какие службы собственного размещения WCF используют внутри на низком уровне в стеке WCF) блокирует мои попытки вставить WWW-Authenticate
заголовок самогенерируемый 401 Unauthorized
ответ. Зачем?
Я могу заставить аутентификацию работать, если я забуду об этом WWW-Authenticate
заголовок (что, кажется, Microsoft также). Но это проблема. Если я не отправлю обратно WWW-Authenticate
заголовок, то веб-браузер не будет отображать стандартный диалог входа в систему. Пользователь просто столкнется с 401 Unauthorized
Страница с ошибкой без возможности войти в систему.
Службы REST должны быть доступны как для компьютеров, так и для людей (по крайней мере, на уровне запросов GET). Поэтому я чувствую, что WCF REST здесь не соответствует фундаментальной части REST. Кто-нибудь согласен со мной?
Кто-нибудь получил базовую аутентификацию, работающую с REST-службой WCF? Если да, то как ты это сделал?
PS: Очевидно, что мои намерения использовать небезопасную базовую аутентификацию основаны на том, что я бы также использовал HTTPS/SSL для своей службы. Но это другое дело.
PPS: я пробовал WCF REST Contrib ( http://wcfrestcontrib.codeplex.com/), и это та же проблема. Похоже, что эта библиотека не была протестирована в сценариях собственного размещения.
Благодарю.
3 ответа
К сожалению, я определил (анализируя исходный код 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) нигде не упоминает об использовании 403 Forbidden
код состояния. Фактически, то, что он действительно должен сказать по этому вопросу, таково:
Если исходный сервер не желает принимать учетные данные, отправленные с запросом, он ДОЛЖЕН вернуть ответ 401 (неавторизованный). Ответ ДОЛЖЕН включать поле заголовка WWW-Authenticate, содержащее, по крайней мере, одну (возможно, новую) задачу, применимую к запрашиваемому ресурсу.
WCF не делает ни того, ни другого. Это ни правильно не отправляет 401 Unauthorized
код состояния. И при этом это не включает WWW-Authenticate
заголовок.
Теперь, чтобы найти пистолет для курения в исходном коде WCF:
Я обнаружил, что в HttpRequestContext
класс это метод, называемый ProcessAuthentication
, который содержит следующее (выдержка):
if (!authenticationSucceeded)
{
SendResponseAndClose(HttpStatusCode.Forbidden);
}
Я защищаю Microsoft от многих вещей, но это неоправданно.
К счастью, у меня это работает на "приемлемом" уровне. Это просто означает, что если пользователь случайно ввел свое имя пользователя / пароль неправильно, то единственный способ получить еще одну попытку - полностью закрыть свой веб-браузер и перезапустить его, чтобы повторить попытку. Все, потому что WCF не отвечает на неудачную попытку аутентификации с 401 Unauthorized
и WWW-Authenticate
заголовок согласно спецификации.
Я нашел решение, основанное на этой ссылке и на этом.
Первый - переопределить метод 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>
Да, вы можете обеспечить базовую аутентификацию для служб WCF на основе REST. Однако есть несколько шагов, которые вы должны выполнить, чтобы получить полное и безопасное решение, и пока большинство ответов - это фрагменты всех необходимых частей.
Сконфигурируйте свою собственную размещаемую службу так, чтобы сертификат SSL был привязан к порту, на котором вы размещаете свою службу WCF. Это очень отличается от применения SSL-сертификата при использовании управляемого хостинга через что-то вроде IIS. Вы должны применить сертификат SSL с помощью утилиты командной строки. Вы НЕ хотите использовать базовую аутентификацию в службе REST без использования SSL, поскольку учетные данные в заголовке не защищены. Вот (2) подробные сообщения, которые я написал о том, как это сделать. Ваш вопрос слишком велик, чтобы разместить все подробности в сообщении на форуме, поэтому я предоставляю ссылки с подробной информацией и пошаговыми инструкциями:
Применение и использование сертификата SSL с собственной службой WCF
Создание службы WCF RESTful и ее защита с использованием HTTPS через SSL
Настройте ваш сервис на использование обычной аутентификации. Это также решение, состоящее из нескольких частей. Во-первых, настройка вашего сервиса для использования обычной аутентификации. Второе - создать customUserNamePasswordValidatorType и проверить учетные данные для аутентификации клиента. Я вижу, что последний пост ускользнул от этого, однако он не использовал HTTPS и является лишь 1 очень маленькой частью решения; будьте осторожны с рекомендациями, которые не предоставляют комплексного решения, включая конфигурацию и безопасность. Последний шаг заключается в рассмотрении контекста безопасности для предоставления авторизации на уровне метода, если это необходимо. В следующем посте, который я написал, вы шаг за шагом узнаете, как настраивать, аутентифицировать и авторизовать ваших клиентов.
Службы RESTful: аутентификация клиентов с использованием базовой аутентификации
Это комплексное решение, необходимое для использования базовой аутентификации с автономными службами WCF.