Самостоятельная служба 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.