自托管的WCF REST服务和基本身份验证

10

我创建了一个自托管的WCF REST服务(使用了WCF REST Starter Kit Preview 2中的一些额外功能),目前一切正常。

现在我想为该服务添加基本认证,但是WCF堆栈中存在一些障碍,阻止我这样做。

看起来,HttpListener(自托管的WCF服务在WCF堆栈中内部使用)正在阻止我在自动生成的401 Unauthorized响应上插入WWW-Authenticate头。为什么呢?

如果我忽略这个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/),但它也存在相同的问题。看来该库在自托管场景中尚未经过测试。

谢谢。

3个回答

12

经过分析 WCF 参考源代码和使用 Fiddler 工具进行 HTTP 会话嗅探,我不幸地发现这是 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. 浏览器重新发送带有适当的 Authentication 头的 GET 请求。
  5. 如果凭证正确,则 Web 服务器响应 200 OK。一切正常。但是,如果凭证错误,则 WCF 响应 403 Forbidden 并不包含任何其他头,例如 WWW-Authenticate

当浏览器获得 403 Forbidden 状态时,它不会将其视为身份验证失败尝试。这个状态代码旨在通知浏览器它试图访问的 URL 是禁止访问的。它与身份验证没有任何关系。这具有可怕的副作用,即当用户输入其用户名/密码不正确(并且服务器拒绝)时,Web 浏览器不再提示用户重新输入凭据。实际上,Web 浏览器认为身份验证已成功,因此为会话的其余部分存储这些凭据!

基于此,我寻求澄清:

RFC 2617(http://www.faqs.org/rfcs/rfc2617.html#ixzz0eboUfnrl)没有在任何地方提到使用 403 Forbidden 状态代码。事实上,它实际上关于此事有以下说法:

  

如果源服务器不希望   接受请求中发送的凭据,则应返回 401   (未经授权)响应。响应   必须包括一个包含至少一个新可用的挑战的 WWW-Authenticate 头字段。   适用于所请求的资源。

WCF 都没有这样做。它既没有正确地发送 if (!authenticationSucceeded) { SendResponseAndClose(HttpStatusCode.Forbidden); }

我在许多事情上都支持微软,但这是无法辩护的。

幸运的是,我已经将其工作调整到了“可接受”的水平。这意味着,如果用户意外输入其用户名/密码错误,则重新尝试的唯一方法是完全关闭他们的Web浏览器并重新启动它。这是因为WCF未按照规范以401 UnauthorizedWWW-Authenticate标头回应失败的身份验证尝试。


@serialseb 我没舍得告诉他,我放弃了尝试解决WCF身份验证问题,直接转向使用HttpListener。 - Darrel Miller
大家好 :) Darrel,我看到了你在另一个讨论中的评论,相信我,我已经考虑将其作为一种选项。幸运的是,我们还没有在 WCF REST 上投入太多。所以我们可能会继续前进并放弃它。 - nbevans

5
I've found a solution related to IT technology based on this link and this.
The first step is to override the Validate method in a class called CustomUserNameValidator, which inherits from 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.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>

1

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

  1. 将 SSL 证书绑定到您托管 WCF 服务的端口上,配置自托管服务。这与通过 IIS 等管理托管应用程序时应用 SSL 证书非常不同。您必须使用命令行实用程序来应用 SSL 证书。在 REST 服务中,如果没有使用 SSL,就不要使用基本身份验证,因为标头中的凭据不安全。以下是我撰写的两篇详细文章,介绍了如何执行此操作。您的问题太大了,无法在论坛帖子中提供所有细节,这就是为什么我提供了包含全面细节和逐步说明的链接:

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

    创建 WCF RESTful 服务并使用 HTTPS Over SSL 安全保护它

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

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接