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

时间:2010-02-04 09:35:13

标签: wcf authentication rest basic-authentication wcf-rest-contrib

我已经创建了一个自托管的WCF REST服务(其中一些来自WCF REST Starter Kit Preview 2)。这一切都很好。

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

HttpListener(自托管WCF服务在WCF堆栈中内部使用较低级别)似乎阻止了我在自生成{WWW-Authenticate标头上的尝试。{ 1}}回应。为什么呢?

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

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

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

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

PPS:我已经尝试过WCF REST Contrib(http://wcfrestcontrib.codeplex.com/),但问题完全相同。看来这个库尚未在自托管方案中进行测试。

感谢。

3 个答案:

答案 0 :(得分: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标头。
  6. 我的WCF服务实际上发生了什么:

    1. 浏览器发送GET请求,但不知道甚至需要密码。
    2. WCF注意到请求中没有Authentication标头,并盲目拒绝401 Unauthorized状态的请求,并包含WWW-Authenticate标头。到目前为止一切正常。
    3. 浏览器会提示用户输入凭据。还是正常的。
    4. 浏览器重新发送GET请求,包括相应的Authentication标题。
    5. 如果凭据正确,则Web服务器将以200 OK响应。一切都很好。 但是,如果凭据错误,则WCF会以403 Forbidden回复,并且不包含任何其他标头,例如WWW-Authenticate
    6. 当浏览器获得403 Forbidden状态时,它不会认为这是一次失败的身份验证尝试。此状态代码旨在通知浏览器其尝试访问的URL不受限制。它与任何方式的身份验证无关。这具有可怕的副作用,当用户错误地键入其用户名/密码(并且服务器拒绝403)时,Web浏览器不会重新提示用户再次键入其凭据。实际上,Web浏览器认为身份验证已成功,因此会在会话的其余部分存储这些凭据!

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

      RFC 2617(http://www.faqs.org/rfcs/rfc2617.html#ixzz0eboUfnrl)未提及使用403 Forbidden状态代码的任何地方。事实上,它在这个问题上实际上要说的是:

        

      如果原始服务器不希望   接受用a发送的凭证   请求,它应该返回401   (未经授权)回复。响应   必须包含WWW-Authenticate标头   包含至少一个的字段   (可能是新的)挑战适用于   请求的资源。

      WCF没有这些。它既没有正确发送401 Unauthorized状态代码。它也不包括WWW-Authenticate标题。

      现在在WCF源代码中找到吸烟枪:

      我发现在HttpRequestContext类中有一个名为ProcessAuthentication的方法,其中包含以下内容(摘录):

      if (!authenticationSucceeded) 
      {
         SendResponseAndClose(HttpStatusCode.Forbidden);
      }
      

      我在许多方面为微软辩护,但这是不可原谅的。

      幸运的是,我让它达到了“可接受”的水平。这只是意味着如果用户意外地错误地输入了他们的用户名/密码,那么获得另一次尝试的唯一方法是完全关闭他们的Web浏览器并重新启动它以重试。所有这些都是因为根据规范,WCF 使用401 UnauthorizedWWW-Authenticate标头响应失败的身份验证尝试。

答案 1 :(得分:6)

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

第一种方法是覆盖名为 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

第二个是在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>

答案 2 :(得分:1)

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

  1. 将您的自托管服务配置为将SSL证书绑定到您托管WCF服务的端口。这与通过IIS之类的托管主机使用SSL证书非常不同。您必须使用命令行实用程序应用SSL证书。您不要想要在没有使用SSL的REST服务上使用基本身份验证,因为标头中的凭据不安全。以下是(2)我在上写的详细帖子如何做到这一点。你的问题太大了,不能在论坛帖子上找到所有细节,所以这就是为什么我提供链接的全面细节和分步说明:

    Applying and Using a SSL Certificate With A Self-Hosted WCF Service

    Creating a WCF RESTful Service And Secure It Using HTTPS Over SSL

  2. 配置您的服务以使用基本身份验证。这也是一个多部分的解决方案。第一是配置您的服务以使用基本身份验证。第二种是创建'customUserNamePasswordValidatorType'并检查凭据以验证客户端。我看到最后一篇文章被忽略了,但它确实使用HTTPS并且只是解决方案的一小部分;注意 not 提供包含配置安全性的端到端解决方案的指导。最后一步是查看安全上下文,以便在需要时在方法级别提供授权。我写的以下文章将逐步介绍如何配置,验证,授权您的客户。

    RESTful Services: Authenticating Clients Using Basic Authentication

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