【问题标题】:Adding basic HTTP auth to a WCF REST service将基本 HTTP 身份验证添加到 WCF REST 服务
【发布时间】:2011-06-10 14:05:43
【问题描述】:

我有一个 WCF HTTP REST 服务,我将它与一个使用不同编程语言的 HTTP 客户端绑定在一起,该客户端编写自己的自定义 HTTP。 我想为我的 WCF 服务添加 WWW-Authenticate 基本身份验证支持。

我的方法如下所示:

[WebInvoke(UriTemplate = "widgets", Method = "POST")]
public XElement CreateWidget(XElement e)   
{
...
}

我是否有可能以某种方式过滤传入的 HTTP 请求,以便在它遇到每个 REST 方法(如上面的 CreateWidget)之前检查有效的基本身份验证字符串? 注意:我的身份验证信息存储在我的数据库中。

基本上我想在请求标头中检查这一点: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== 然后我可以自己解析该字符串并验证数据库中的 u/p。

web.config文件如下:

<?xml version="1.0"?>
<configuration>

  <connectionStrings>
    <add name="DatabaseConnectionString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=Database;Integrated Security=True" providerName="System.Data.SqlClient" />
  </connectionStrings>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
    <httpRuntime maxRequestLength="10485760" />
  </system.web>

  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
    </modules>
  </system.webServer>

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <standardEndpoints>
      <webHttpEndpoint>
        <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true" maxReceivedMessageSize="1048576" maxBufferSize="1048576" />
      </webHttpEndpoint>
    </standardEndpoints>
  </system.serviceModel>

</configuration>

【问题讨论】:

    标签: wcf http authentication


    【解决方案1】:

    我还对 REST HTTP WCF 服务中的自定义身份验证感兴趣,并最终让它发挥作用。

    话虽如此,我的代码将为您提供一种使其正常工作的方法,但我建议您阅读本指南,该指南更深入地解释了所有内容:http://wcfsecurityguide.codeplex.com/

    首先,将 Web.Config 的 system.web 部分更改为如下所示:

    <system.web>
      <compilation debug="true" targetFramework="4.0" />
      <httpRuntime maxRequestLength="10485760" />
      <authentication mode="None"></authentication>
      <httpModules>
        <add name="BasicAuthenticationModule" type="YourNamespace.UserNameAuthenticator" />
      </httpModules>
    </system.web>
    

    然后将另一个文件添加到您的项目中:UserNameAuthenticator.cs

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web;
    using System.Web.Security;
    using System.Security.Principal;
    using System.ServiceModel.Activation;
    
    namespace YourNamespace
    {
        public class UserNameAuthenticator : IHttpModule
        {
            public void Dispose()
            {
            }
    
            public void Init(HttpApplication application)
            {
                application.AuthenticateRequest += new EventHandler(this.OnAuthenticateRequest);
                application.AuthorizeRequest += new EventHandler(this.OnAuthorizationRequest);
                application.EndRequest += new EventHandler(this.OnEndRequest);
            }
    
            public bool CustomAuth(string username, string password)
            {
                //TODO: Implement your custom auth logic here
                return true;
            }
    
            public string[] GetCustomRoles(string username)
            {
                return new string[] { "read", "write" };
            }
    
            public void OnAuthorizationRequest(object source, EventArgs eventArgs)
            {
                HttpApplication app = (HttpApplication)source;
                //If you want to handle authorization differently from authentication
            }
    
            public void OnAuthenticateRequest(object source, EventArgs eventArgs)
            {
                HttpApplication app = (HttpApplication)source;
                //the Authorization header is checked if present
                string authHeader = app.Request.Headers["Authorization"];
                if (!string.IsNullOrEmpty(authHeader))
                {
                    string authStr = app.Request.Headers["Authorization"];
                    if (authStr == null || authStr.Length == 0)
                    {
                        // No credentials; anonymous request
                        return;
                    }
                    authStr = authStr.Trim();
                    if (authStr.IndexOf("Basic", 0) != 0)
                    {
                        //header not correct we do not authenticate
                        return;
                    }
    
                    authStr = authStr.Trim();
                    string encodedCredentials = authStr.Substring(6);
                    byte[] decodedBytes = Convert.FromBase64String(encodedCredentials);
                    string s = new ASCIIEncoding().GetString(decodedBytes);
                    string[] userPass = s.Split(new char[] { ':' });
                    string username = userPass[0];
                    string password = userPass[1];
                    //the user is validated against the SqlMemberShipProvider
                    //If it is validated then the roles are retrieved from the
                    //role provider and a generic principal is created
                    //the generic principal is assigned to the user context
                    // of the application
                    if (CustomAuth(username, password))
                    {
                        string[] roles = GetCustomRoles(username);
                        app.Context.User = new GenericPrincipal(new
                        GenericIdentity(username, "Membership Provider"), roles);
                    }
                    else
                    {
                        DenyAccess(app);
                        return;
                    }
                }
                else
                {
                    //the authorization header is not present
                    //the status of response is set to 401 and it ended
                    //the end request will check if it is 401 and add
                    //the authentication header so the client knows
                    //it needs to send credentials to authenticate
                    app.Response.StatusCode = 401;
                    app.Response.End();
                }
            }
    
            public void OnEndRequest(object source, EventArgs eventArgs)
            {
                if (HttpContext.Current.Response.StatusCode == 401)
                {
                    //if the status is 401 the WWW-Authenticated is added to
                    //the response so client knows it needs to send credentials
                    HttpContext context = HttpContext.Current;
                    context.Response.StatusCode = 401;
                    context.Response.AddHeader("WWW-Authenticate", "Basic Realm");
                }
            }
            private void DenyAccess(HttpApplication app)
            {
                app.Response.StatusCode = 401;
                app.Response.StatusDescription = "Access Denied";
                // error not authenticated
                app.Response.Write("401 Access Denied");
                app.CompleteRequest();
            }
        } // End Class
    } //End Namespace
    

    【讨论】:

    • 我已按照您的建议执行了这些步骤,但该模块从未被调用。我尝试在 IIS 上添加基本身份验证,但仍然没有调用该模块。我错过了什么吗?
    • 我不知道为什么它对我有用,但可能与此有关:stackoverflow.com/questions/355261/…(我正在通过 VS 调试自动托管 Web 服务器进行测试)
    • 这里重要的是要记住,通过使用 Web.Config 文件来定义您的身份验证方法,您需要明确禁止 IIS 进行身份验证。因此,在 IIS 中: - 您必须勾选“启用匿名访问” - 您不得勾选“集成 Windows 身份验证” - 您不得勾选“Windows 域服务器的摘要式身份验证” - 您不得勾选“基本身份验证(密码在明文)”(令人困惑的是,身份验证由 web.config 文件提供) - 您不得勾选“.NET Passport 身份验证”
    • 我会检查输入验证:在s.Split(new char[] { ':' }) 之后,我会确保元素的数量正好是 2。
    【解决方案2】:

    我遇到过类似的问题,发现了很多不同的方法,尤其是跨域调用,加上基本身份验证似乎有点挑战。例如,Jquery 首先发出一个 OPTIONS 调用来验证它是否允许 POST。 Wcf 通常会拒绝此请求,您会收到一个奇怪的错误。

    我终于搞定了,你可以从我的博客下载示例代码:http://sameproblemmorecode.blogspot.com/2011/10/creating-secure-restfull-wcf-service.html

    【讨论】:

      【解决方案3】:

      此外,Chrome 不会加载登录对话框,除非您在 OnEndRequest 方法中将“BasicRealm”更改为“BasicRealm=site”:

          public void OnEndRequest(object source, EventArgs eventArgs)
          {
              if (HttpContext.Current.Response.StatusCode == 401)
              {
                  //if the status is 401 the WWW-Authenticated is added to 
                  //the response so client knows it needs to send credentials 
                  HttpContext context = HttpContext.Current;
                  context.Response.StatusCode = 401;
                  context.Response.AddHeader("WWW-Authenticate", "Basic Realm=site");
              }
          }
      

      谢谢,这是一个非常简单的解决方案。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-04
        • 1970-01-01
        相关资源
        最近更新 更多