我有一个WCF HTTP REST服务,我使用不同编程语言的HTTP客户端编写自己的自定义HTTP。 我想为我的WCF服务添加WWW-Authenticate基本身份验证支持。
我的方法如下:
[WebInvoke(UriTemplate = "widgets", Method = "POST")]
public XElement CreateWidget(XElement e)
{
...
}
我是否有可能以某种方式过滤传入的HTTP请求,以便我可以检查有效的Basic auth字符串,然后才能访问上面的CreateWidget
等每个REST方法?
注意:我的身份验证信息在我的数据库中是stord。
基本上我想在请求标头中检查这个:
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>
答案 0 :(得分:8)
我还对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
答案 1 :(得分:3)
我遇到了类似的问题并发现了许多不同的方法,尤其是跨域调用,以及基本身份验证似乎是一个挑战。例如,Jquery首先发出一个OPTIONS调用来验证它是否允许POST。 Wcf通常拒绝此请求,您会收到一个奇怪的错误。
我终于搞定了,你可以从我的博客下载示例代码:http://sameproblemmorecode.blogspot.com/2011/10/creating-secure-restfull-wcf-service.html
答案 2 :(得分:2)
除此之外,除非您在OnEndRequest方法中将“BasicRealm”更改为“BasicRealm = site”,否则Chrome不会加载登录对话框:
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");
}
}
谢谢,这是一个简单的解决方案。