Самостоятельная служба 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 работает не так, как любой другой веб-сайт, использующий обычную аутентификацию.

Чтобы было ясно, это то, что ДОЛЖНО случиться:

  1. Браузер отправляет GET не зная, что пароль даже нужен.
  2. Веб-сервер отклоняет запрос с 401 Unauthorized статус и включает в себя WWW-Authenticate заголовок, содержащий информацию о приемлемых методах аутентификации.
  3. Браузер предлагает пользователю ввести учетные данные.
  4. Браузер повторно отправляет GET запрос и включает в себя соответствующие Authentication заголовок с учетными данными.
  5. Если учетные данные были правильными, веб-сервер отвечает 200 OK и веб-страница. Если учетные данные были неправильными, веб-сервер отвечает 401 Unauthorized и включает в себя то же самое WWW-Authenticate заголовок, который он сделал на шаге #2.

Что на самом деле происходило с моей службой WCF, так это:

  1. Браузер отправляет GET не зная, что пароль даже нужен.
  2. WCF уведомлений нет Authentication заголовок в запросе и слепо отклоняет запрос с 401 Unauthorized статус и включает в себя WWW-Authenticate заголовок. Пока все нормально.
  3. Браузер запрашивает у пользователя учетные данные. Все еще нормально.
  4. Браузер повторно отправляет GET запрос, включая соответствующий Authentication заголовок.
  5. Если учетные данные были правильными, веб-сервер отвечает 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. Однако есть несколько шагов, которые вы должны выполнить, чтобы получить полное и безопасное решение, и пока большинство ответов - это фрагменты всех необходимых частей.

  1. Сконфигурируйте свою собственную размещаемую службу так, чтобы сертификат SSL был привязан к порту, на котором вы размещаете свою службу WCF. Это очень отличается от применения SSL-сертификата при использовании управляемого хостинга через что-то вроде IIS. Вы должны применить сертификат SSL с помощью утилиты командной строки. Вы НЕ хотите использовать базовую аутентификацию в службе REST без использования SSL, поскольку учетные данные в заголовке не защищены. Вот (2) подробные сообщения, которые я написал о том, как это сделать. Ваш вопрос слишком велик, чтобы разместить все подробности в сообщении на форуме, поэтому я предоставляю ссылки с подробной информацией и пошаговыми инструкциями:

    Применение и использование сертификата SSL с собственной службой WCF

    Создание службы WCF RESTful и ее защита с использованием HTTPS через SSL

  2. Настройте ваш сервис на использование обычной аутентификации. Это также решение, состоящее из нескольких частей. Во-первых, настройка вашего сервиса для использования обычной аутентификации. Второе - создать customUserNamePasswordValidatorType и проверить учетные данные для аутентификации клиента. Я вижу, что последний пост ускользнул от этого, однако он не использовал HTTPS и является лишь 1 очень маленькой частью решения; будьте осторожны с рекомендациями, которые не предоставляют комплексного решения, включая конфигурацию и безопасность. Последний шаг заключается в рассмотрении контекста безопасности для предоставления авторизации на уровне метода, если это необходимо. В следующем посте, который я написал, вы шаг за шагом узнаете, как настраивать, аутентифицировать и авторизовать ваших клиентов.

    Службы RESTful: аутентификация клиентов с использованием базовой аутентификации

Это комплексное решение, необходимое для использования базовой аутентификации с автономными службами WCF.

Другие вопросы по тегам