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

发布于 2024-08-20 01:51:59 字数 855 浏览 13 评论 0原文

我创建了一个自托管的 WCF REST 服务(带有 WCF REST Starter Kit Preview 2 中的一些额外内容)。这一切工作正常。

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

看来 HttpListener(自托管 WCF 服务在 WCF 堆栈中的低级别内部使用)正在阻止我尝试在自行生成的 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/)有完全相同的问题。该库似乎尚未在自托管场景中进行测试。

谢谢。

I've created a self-hosted WCF REST service (with some extra's from WCF REST Starter Kit Preview 2). This is all working fine.

I'm now trying to add Basic authentication to the service. But I'm hitting some rather large roadblocks in the WCF stack which is preventing me from doing this.

It appears that the HttpListener (which self-hosted WCF services use internally at a low level in the WCF stack) is blocking my attempts to insert a WWW-Authenticate header on a self-generated 401 Unauthorized response. Why?

I can get the authentication working if I forget about this WWW-Authenticate header (which it seems Microsoft did as well). But that's the issue. If I don't send back a WWW-Authenticate header then the web browser won't display its standard "logon" dialog. The user will merely be faced with a 401 Unauthorized error page with no way to actually log on.

REST services should be accessible to both computers and humans (well at least on the GET request level). Therefore, I feel that WCF REST is not complying with a fundamental part of REST here. Does anyone agree with me?

Has anyone got Basic authentication working with a self-hosted WCF REST service? If so, how did you do it?

PS: Obviously my intentions to use unsecure Basic authentication are on the premise that I'd also get HTTPS/SSL working for my service too. But that's another matter.

PPS: I've tried WCF REST Contrib (http://wcfrestcontrib.codeplex.com/) and that has exactly the same issue. It appears this library has not been tested in self-hosted scenarios.

Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

软糯酥胸 2024-08-27 01:51:59

不幸的是,我已经确定(通过分析 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. 浏览器重新发送包含适当的 Authentication 标头的 GET 请求。
  5. 如果凭据正确,Web 服务器将响应 200 OK。一切都很好。
    但是,如果凭据错误,WCF 将响应 403 Forbidden,并且不包含任何其他标头,例如 WWW-Authenticate

当浏览器获得 403 Forbidden 状态时,它不会认为这是一次失败的身份验证尝试。此状态代码旨在通知浏览器它尝试访问的 URL 不受限制。它与身份验证没有任何关系。这会产生可怕的副作用,即当用户错误地输入用户名/密码(并且服务器以 403 拒绝)时,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);
}

我捍卫 Microsoft在很多事情上,但这是站不住脚的。

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

Unfortunately I have determined (by analysing the WCF reference source code and the help of the Fiddler tool for HTTP session sniffing) that this is a bug in the WCF stack.

Using Fiddler, I noticed that my WCF service was behaving unlike any other web site which uses Basic authentication.

To be clear, this is what SHOULD happen:

  1. Browser sends GET request with no knowledge that a password is even needed.
  2. Web server rejects request with a 401 Unauthorized status and includes a WWW-Authenticate header containing information about acceptable authentication methods.
  3. Browser prompts user to enter credentials.
  4. Browser resends GET request and includes appropriate Authentication header with the credentials.
  5. If the credentials were correct, the web server responds with 200 OK and the web page.
    If the credentials were wrong, the web server responds with 401 Unauthorized and includes the same WWW-Authenticate header that it did in Step #2.

What was ACTUALLY happening with my WCF service was this:

  1. Browser sends GET request with no knowledge that a password is even needed.
  2. WCF notices there is no Authentication header in the request and blindly rejects request with a 401 Unauthorized status and includes a WWW-Authenticate header. All normal so far.
  3. Browser prompts user for credentials. Still normal.
  4. Browser resends GET request including the appropriate Authentication header.
  5. If the credentials were correct, the web server responds with 200 OK. All is fine.
    If the credentials were wrong however, WCF responds with 403 Forbidden and does not include any additional headers such as WWW-Authenticate.

When the browser gets the 403 Forbidden status it does not perceive this to be a failed authentication attempt. This status code is intended to inform the browser that the URL it tried to access is off limits. It doesn't relate to authentication in any way. This has the terrible side affect that when the user types their username/password incorrectly (and the server rejects with 403) then the web browser doesn't reprompt the user to type their credentials again. In fact the web browser believes authentication has succeeded and so stores those credentials for the rest of the session!

With this in mind, I sought clarification:

The RFC 2617 (http://www.faqs.org/rfcs/rfc2617.html#ixzz0eboUfnrl) does not mention anywhere the use of the 403 Forbidden status code. In fact, what it actually has to say on the matter is the following:

If the origin server does not wish to
accept the credentials sent with a
request, it SHOULD return a 401
(Unauthorized) response. The response
MUST include a WWW-Authenticate header
field containing at least one
(possibly new) challenge applicable to
the requested resource.

WCF does neither of these. It neither correctly sends an 401 Unauthorized status code. Nor does it include a WWW-Authenticate header.

Now to find the smoking gun within the WCF source code:

I discovered that in the HttpRequestContext class is a method called ProcessAuthentication, which contains the following (excerpt):

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

I defend Microsoft on a lot of things but this is indefensible.

Fortunately, I have got it working to an "acceptable" level. It just means that if the user accidently enters their username/password incorrectly then the only way to get another attempt is to fully close their web browser and restart it to retry. All because WCF is not responding to the failed authentication attempt with a 401 Unauthorized and a WWW-Authenticate header as per the specification.

他夏了夏天 2024-08-27 01:51:59

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

第一个是重写名为 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>

I've found the solution based in this link and this.

The first is to override the method Validate in a class called CustomUserNameValidator wich 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

The trick was change the attribute of the exception "HttpStatusCode" to HttpStatusCode.Unauthorized

The second is create the serviceBehavior in the App.config as follows:

<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>

Finally we must to specify the configuration for the webHttpBinding and apply it to the endpoint:

<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>
悲念泪 2024-08-27 01:51:59

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

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

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

    创建 WCF RESTful使用基于 SSL 的 HTTPS 提供服务并保护其安全

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

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

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

Yes you can provide Basic authentication for REST based WCF services. However there are several steps which you must follow to have a complete and secure solution and thus far most responses are fragments of all the pieces needed.

  1. Configure your self-hosted service to have a SSL certificate bound to the port which you are hosting your WCF service on. This is very different than applying a SSL cert when using managed hosting through something like IIS. You have to apply the SSL certificate using a command line utility. You DO NOT want to use Basic Authentication on a REST service without using SSL because the credentials in the header on not secure. Here are (2) detailed posts that I wrote on exactly how to do this. Your question is too big to have all the details on a forum post, so that is why I am providing the links with comprehensive details and step by step instructions:

    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. Configure your service to use Basic authentication. This is a multi-part solution as well. 1st is configuring your service to use Basic authentication. The second is to create a 'customUserNamePasswordValidatorType' and inspect the credentials to authenticate the client. I see the last post eluded to this, however it did not use HTTPS and is only 1 very small part of the solution; be careful of guidance that does not provided an end-to-end solution inclusive of configuration and security. The last step is to look at the security context to provide authorization at the method level if needed. The following post I wrote takes you step-by-step on how to configure, authenticate, and authorize your clients.

    RESTful Services: Authenticating Clients Using Basic Authentication

This is the end-to-end solution needed for using Basic Authentication with self-hosted WCF services.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文