在构建基于WCF REST 4.0的服务时,实现用户认证和授权是一个常见的需求。本文将介绍如何在WCF REST服务中实现基于服务类型的用户认证和基于角色的授权。
在WCF服务中,有多种方式可以进行用户认证和授权。本文示例中,用户认证的Cookie将由登录页面创建,并用于后续对REST服务的请求进行授权。
通常,使用Principal Permission属性来授权用户特定角色。例如:
[WebGet(UriTemplate = "")]
[PrincipalPermission(SecurityAction.Demand, Role = "Admin")]
public List<SampleItem> GetCollection(){}
但是,即使用户使用Membership提供者进行认证,并且HTTPContext.Current.User.Identity在服务级别可用,Principal Permission属性仍然会抛出安全异常。这是因为Principal Permission属性检查的是System.Threading.Thread.CurrentPrincipal.Identity,而不是HTTPContext.Identity。
为了解决这个问题,需要为WCF服务创建自定义Principal和授权策略。然后,将使用ServiceBehaviour将此策略与WCF REST服务挂钩。
以下是自定义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提供者来验证用户是否在特定角色中。也可以实现不使用Membership提供者的自定义实现。
现在创建一个授权策略,将自定义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; }
}
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
IIdentity client = HttpContext.Current.User.Identity;
evaluationContext.Properties["Principal"] = new CustomPrincipal(client);
return true;
}
}
仔细查看,自定义Principal是使用HTTPContext.Identity创建的,该Identity是在用户使用Membership提供者进行认证并设置认证Cookie后创建的。
这可以通过在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,并且授权策略被添加到servicehost。
确保将服务行为作为属性添加到服务类。
[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属性来控制授权的粒度。