WCF REST 4.0 授权与基于表单的身份验证 (SetAuthCookie)






4.88/5 (22投票s)
如何创建自定义授权策略并返回 HTTPContext Identity 用于授权。
引言
Windows Communication Foundation 提供了大量方法来验证用户、检查授权,基于服务类型,这使得为 WCF REST 4.0 实现简单的基于表单的身份验证和基于角色的授权变得相当令人困惑。
注意:本文假定 WCF REST 服务与 ASP.NET 应用程序一起托管,并共享相同的 web.config。 确保在 web.config 文件中启用了表单身份验证。
背景
在 WCF 服务中验证和授权用户有很多种方法,但在本示例中,身份验证 Cookie 已经由登录页面创建,后续对 REST 服务的请求将使用该 Cookie 进行授权。
Using the Code
授权用户特定角色的典型方法是使用 PrincipalPermission 属性。 类似于这样
WebGet(UriTemplate = "")]
[PrincipalPermission(SecurityAction.Demand, Role="Admin")]
public List<SampleItem> GetCollection(){}
但是,即使在用户使用 Membership provider 验证并通过 HTTPContext.Current.User.Identity
获得上下文,并且该上下文在服务级别可用,PrincipalPermission 属性仍然会抛出安全异常。
原因是 PrincipalPermission 属性检查的是 System.Threading.Thread.CurrentPrincipal.Identity
,而不是 HTTPContext
Identity。
为了解决这个问题,我们必须为 WCF 服务创建自定义 Principal 和授权策略。 然后,该策略将使用 ServiceBehaviour
挂接到 WCF REST 服务。
自定义 Principal
以下是自定义 Principal 的代码
public class CustomPrincipal: IPrincipal
{
private IIdentity _identity;
public IIdentity Identity
{
get
{
return _identity;
}
}
public CustomPrincipal(IIdentity identity)
{
_identity = identity;
}
public bool IsInRole(string role)
{
return Roles.IsUserInRole(role);
}
}
这里使用了 ASP.NET Membership Role provider 来验证用户是否属于特定角色。 我们可以拥有自己的自定义实现,不使用 Membership provider。
授权策略
现在创建一个授权策略,将自定义 Principal 设置为评估上下文
public class AuthorizationPolicy : IAuthorizationPolicy
{
string id = Guid.NewGuid().ToString();
public string Id
{
get { return this.id; }
}
public System.IdentityModel.Claims.ClaimSet Issuer
{
get { return System.IdentityModel.Claims.ClaimSet.System; }
}
// this method gets called after the authentication stage
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
// get the authenticated client identity
IIdentity client = HttpContext.Current.User.Identity;
// set the custom principal
evaluationContext.Properties["Principal"] = new CustomPrincipal(client);
return true;
}
}
仔细观察,自定义 Principal 是使用用户使用 Membership provider 验证身份后创建的 HTTPContext
Identity 创建的,并且在验证用户后设置了身份验证 Cookie。 类似于这样
FormsAuthentication.SetAuthCookie(username, false);
将授权策略附加到 WCF
这可以通过在 web.config 文件中创建一个服务行为来完成。 但是,这里我通过实现 IServiceBehavior
并将授权策略附加到它来创建自定义服务行为。
[AttributeUsage(AttributeTargets.Class)]
public class SecurityBehaviorAttribute : Attribute, IServiceBehavior
{
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase)
{
List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>();
policies.Add(new AuthorizationPolicy());
serviceHostBase.Authorization.ExternalAuthorizationPolicies =
policies.AsReadOnly();
ServiceAuthorizationBehavior bh =
serviceDescription.Behaviors.Find<ServiceAuthorizationBehavior>();
if (bh != null)
{
bh.PrincipalPermissionMode = PrincipalPermissionMode.Custom;
}
else
throw new NotSupportedException();
}
public void AddBindingParameters(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase,
System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints,
System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }
public void Validate(ServiceDescription serviceDescription,
System.ServiceModel.ServiceHostBase serviceHostBase) { }
}
这里,ServiceAuthorizationBehavior PrincipalPermissionMode
设置为 Custom
,并且 Authorization
策略被添加到 servicehost
。
服务代码
确保将服务行为作为属性添加到 service
类。
[ServiceContract]
[AspNetCompatibilityRequirements(RequirementsMode =
AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[SecurityBehavior]
public class Service1 {
[WebGet(UriTemplate = "")]
[PrincipalPermission(SecurityAction.Demand, Role="Admin")]
public List<SampleItem> GetCollection()
{
var value = System.Web.HttpContext.Current.User.Identity.IsAuthenticated;
return new List<SampleItem>() { new SampleItem()
{ Id = 1, StringValue = "Hello" } };
}
}
就这样。 现在我们可以将 PrincipalPermission
属性添加到任何 Web 方法,并授权用户访问特定角色。 我们可以还实现自定义 PrincipalPermission
属性来控制授权的粒度。
注意:我们还可以创建一个 Authentication
服务来验证用户名密码,并在验证后创建身份验证 Cookie。 这里假设 WCF REST 与 Web 应用程序一起托管,因此共享上下文。
请告诉我是否有更好的方法来实现相同的功能,而无需在每次请求中提供用户名和密码。