11

我创建了一个自托管的 WCF REST 服务(带有一些来自 WCF REST Starter Kit Preview 2 的额外服务)。这一切都很好。

我现在正在尝试将基本身份验证添加到服务中。但是我在 WCF 堆栈中遇到了一些相当大的障碍,这阻止了我这样做。

似乎HttpListener(自托管的 WCF 服务在 WCF 堆栈的低级别内部使用)正在阻止我尝试WWW-Authenticate在自生成的401 Unauthorized响应中插入标头。为什么?

如果我忘记了这个WWW-Authenticate标头(微软似乎也这样做了),我可以让身份验证工作。但这就是问题所在。如果我不发回WWW-Authenticate标头,那么 Web 浏览器将不会显示其标准的“登录”对话框。用户只会面临一个401 Unauthorized错误页面,无法实际登录。

计算机和人类都应该可以访问 REST 服务(至少在 GET 请求级别上)。因此,我觉得 WCF REST 在这里不符合 REST 的基本部分。有人同意我的观点吗?

有没有人使用自托管的 WCF REST 服务获得基本身份验证?如果是这样,你是怎么做到的?

PS:显然,我打算使用不安全的基本身份验证的前提是我也会让 HTTPS/SSL 也为我的服务工作。但那是另一回事。

PPS:我已经尝试过 WCF REST Contrib ( http://wcfrestcontrib.codeplex.com/ ) 并且有完全相同的问题。看来此库尚未在自托管场景中进行测试。

谢谢。

4

3 回答 3

13

不幸的是,我已经确定(通过分析 WCF 参考源代码和用于 HTTP 会话嗅探的 Fiddler 工具的帮助)这是 WCF 堆栈中的一个错误。

使用 Fiddler,我注意到我的 WCF 服务的行为与任何其他使用基本身份验证的网站不同。

需要明确的是,这是应该发生的:

  1. 浏览器GET在不知道甚至需要密码的情况下发送请求。
  2. Web 服务器拒绝带有401 Unauthorized状态的请求,并包含一个WWW-Authenticate包含有关可接受的身份验证方法信息的标头。
  3. 浏览器提示用户输入凭据。
  4. 浏览器重新发送请求并在凭证中GET包含适当的标头。Authentication
  5. 如果凭据正确,则 Web 服务器响应200 OK和网页。如果凭据错误,Web 服务器会响应401 Unauthorized并包含与步骤 #2 中相同的WWW-Authenticate标头。

我的 WCF 服务实际发生的情况是:

  1. 浏览器GET在不知道甚至需要密码的情况下发送请求。
  2. WCF 注意到请求中没有Authentication标头,并盲目拒绝带有401 Unauthorized状态的请求并包含WWW-Authenticate标头。到目前为止一切正常。
  3. 浏览器提示用户输入凭据。还是正常的。
  4. 浏览器重新发送GET请求,包括适当的Authentication标头。
  5. 如果凭据正确,则 Web 服务器以200 OK. 一切皆好。但是,如果凭据错误,WCF 会响应403 Forbidden并且不包含任何其他标头,例如WWW-Authenticate.

当浏览器获得该403 Forbidden状态时,它不会认为这是一次失败的身份验证尝试。此状态代码旨在通知浏览器它尝试访问的 URL 不受限制。它以任何方式与身份验证无关。这有一个可怕的副作用,当用户错误地输入他们的用户名/密码(并且服务器拒绝 403)时,Web 浏览器不会再次提示用户再次输入他们的凭据。事实上,Web 浏览器认为身份验证已成功,因此会在会话的其余部分存储这些凭据!

考虑到这一点,我寻求澄清:

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);
}

我在很多事情上为微软辩护,但这是站不住脚的。

幸运的是,我已经让它达到了“可接受”的水平。这只是意味着如果用户不小心输入了错误的用户名/密码,那么再次尝试的唯一方法是完全关闭他们的网络浏览器并重新启动以重试。这一切都是因为 WCF没有按照规范使用401 Unauthorized和标头响应失败的身份验证尝试。WWW-Authenticate

于 2010-02-04T22:40:23.473 回答
6

我找到了基于此链接的解决方案。

第一个是在一个名为CustomUserNameValidator的类中覆盖方法 Validate ,该类继承自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

第二个是在 App.config 中创建serviceBehavior如下:

<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>
于 2011-06-13T21:08:48.473 回答
1

是的,您可以为基于 REST 的 WCF 服务提供基本身份验证。但是,您必须遵循几个步骤才能获得完整且安全的解决方案,并且到目前为止,大多数响应都是所需的所有部分的片段。

  1. 将您的自托管服务配置为将 SSL 证书绑定到您托管 WCF 服务的端口。这与通过 IIS 等使用托管主机时应用 SSL 证书非常不同。您必须使用命令行实用程序应用 SSL 证书。您不想在不使用 SSL 的情况下在 REST 服务上使用基本身份验证,因为头中的凭据不安全。这里有 (2) 篇详细的帖子,我写了关于如何做到这一点的详细信息。您的问题太大,无法在论坛帖子中包含所有详细信息,因此我提供了包含全面详细信息和分步说明的链接:

    通过自托管 WCF 服务应用和使用 SSL 证书

    创建 WCF RESTful 服务并使用 HTTPS 通过 SSL 对其进行保护

  2. 将您的服务配置为使用基本身份验证。这也是一个多部分的解决方案。第一个是将您的服务配置为使用基本身份验证。第二个是创建一个“customUserNamePasswordValidatorType”并检查凭据以验证客户端。我看到最后一篇文章没有提到这一点,但是它没有使用HTTPS,并且只是解决方案的一小部分;请注意提供端到端解决方案(包括配置安全性)的指南。最后一步是查看安全上下文以在需要时在方法级别提供授权。我写的以下文章将带您逐步了解如何配置、验证授权您的客户端。

    RESTful 服务:使用基本身份验证对客户端进行身份验证

这是将基本身份验证与自托管 WCF 服务结合使用所需的端到端解决方案。

于 2012-06-02T02:43:58.720 回答