我的wcf之旅,WCF Security之RoleProvider

最近在弄WCF Security(Authentication,Authorization)方面的解决方案,我想把各种验证及授权的解决方案分别写出来,一当做自己学习的总结,另外也与各位童鞋们探讨、分享。今天先看看如何用RoleProvider来实现授权。
RoleProvider,最初目的是用于满足ASP.NET的授权要求,对于一般ASP.NET的应用,如果是Website,那么这个客户端认证信息的类型(ClientCredentialType)是没有(None)的。如果是Web Application,启用Forms authentication,相当于ClientCredentialType=UserName,登陆页面提交到服务端,通过配置的MembershipProvider来做验证,然后通过配置的RoleProvider来做授权。
当然了,RoleProvider也可以用于WCF,只是和Web Application的实现有所不同。WCF使用RoleProvider做服务端授权,客户端认证信息的类型可以是Windows,UserName,甚至是Certificate,只要principalPermissionMode设置为UseAspNetRoles,即保证服务线程的CurrentPrincipal的类型为RoleProviderPrincipal,而Web Application则不需要像WCF一样显示指定(,关于Web Application的验证,不在此文讨论的范围)。
RoleProvider依赖于服务线程的RoleProviderPrincipal,而这个IPrincipal,服务端需要在接收到客户端请求时,根据客户端的认证信息及类型写入的。其大致的流程是这样的:
首先,客户端需要提供用户名和密码,到了服务端,WCF会将RoleProviderPrincipal放到所有服务线程中去(System.Threading.Thread.CurrentPrincipal)。那么在服务端代码中,就可以使用System.Threading.Thread.CurrentPrincipal.IsInRole,PrincipalPermissionAttribute或者PrincipalPermission来判断当前用户是否具有某个角色的权限,从而来决定是否执行下面的程序。在这里,角色是个抽象的概念,你可以认为它是用户组,也可以认为它相当于用户的功能组合,都没关系。此外,你可以使用自带的RoleProvider(AspNetSqlRoleProvider,AspNetWindowsTokenRoleProvider)来实现权限判断,也可以自己实现RoleProvider来实现权限控制。由于权限判断的代码使用了统一的形式(唯一产生耦合的地方就是roleName),因为RoleProvider可以在服务端任意设定,这就意味着,权限控制的数据来源可以是windows用户及组、域用户及组、来自sql server的权限数据,等等。换句话说,实现RBAC的方式可以根据自己的实际需要来做选择,除了在具体的方法里指定角色名之外,权限控制的实现,被放到了RoleProvider中,这样就很容易实现权限系统的切换及变动(代码中与权限相关的代码,即便在权限控制的方式发生改变后,也可以最大程度的保持稳定)。而产生耦合的roleName也可以通过配置的方式来解耦。
在使用RoleProvider时,它支持三种权限控制方式:
1)PrincipalPermissionAttribute - Declarative方式。
只需要在指定的方法上使用该特性,[PrincipalPermission(SecurityAction.Demand, Role = "createSalesOrder")],就可以实现无侵入式的权限控制,这种方式越来越流行,可以基于系统中的任意方法,我想这也是未来系统权限控制的一种不错的选择。
在使用这种方式的时候,需要注意一点,如果一个方法所支持的角色或组有多个,需要分清楚是当前用户需要具有所有的组的权限呢?还是只有拥有任意一个组的权限即可执行该方法。如果是后者,方法上的PrincipalPermissionAttribute支持多个,这些role之间的关系就是OR的关系,而如果是AND的关系,你可以考虑建立嵌套组(nesting group)来处理,或者你是自己实现RoleProvider,可以考虑在IsUserInRole方法的RoleName参数上做些扩展,支持一定的语义支持,例如,[PrincipalPermission(SecurityAction.Demand, Role = "createSalesOrder&&printSalesOrder&auditSalesOrder")]。
2)PrincipalPermission - Assertion方式。
通过在方法的开始处插入如下代码段来控制权限,我称之为断言方式。
IPermission createSalesOrderPerm = new PrincipalPermission(null, "createSalesOrder"); createSalesOrderPerm.Demand();
3)System.Threading.Thread.CurrentPrincipal.IsInRole - Imperative方式。
这种方式叫做祈使方式,就是根据是否拥有权限来写相应的操作控制。
这三种方式可有优缺点及使用场景,我比较倾向于Declarative方式,因为这样对代码的侵入性要小,而且方式比较干净。
下面提供一个示例,加以说明。
在服务端的配置中,只要做如下配置:

这就表明服务端将采用RoleProvider的方式进行授权检查。
由于授权依赖于客户端传入用户名及密码,所以,ClientCredentialType可以选择Windows和UserName两种方式,其中,UserName可以支持其他的数据格式,例如你们产品的权限验证不仅仅依赖于用户名和密码,还有公司名,那么你可以把公司名和用户名都放在UserName中,在服务端再做拆解。而且UserName还支持MembershipProvider方式的验证以及其他自定义方式的验证,因此,在这里你可以选择UserName的方式。由于UserName的方式依赖于安全的消息传输,所以安全模式可以选择TransportWithMessageCredential。
这种方式的配置分别如下:
Binding:

Service behavior:

(验证部分可以忽略)
Service:


接下来在服务的接口方法中,直接通过获取当前线程的RoleProviderPrincipal,来判断其是否拥有某角色权限,来确定当前用户是否具有当前方法的操作权限:
bool canReadData = System.Threading.Thread.CurrentPrincipal.IsInRole("createSalesOrder"); // CurrentPrincipal is RoleProviderPrincipal.

如上面所言,推荐使用下面这种方式:
[OperationContract] [PrincipalPermission(SecurityAction.Demand, Role = "createSalesOrder")] int CreateSalesOrder(ISalesOrder order);

这种方式就不推荐了:
IPermission createSalesOrderPerm= new PrincipalPermission(null, "createSalesOrder"); createSalesOrderPerm.Demand();

此外,我在上面的例子中使用的是MyRoleProvider,这个类继承自RoleProvider,只重写了我需要用到的几个方法:
namespace TestBase { public class MyRoleProvider : RoleProvider { public override string[] GetAllRoles() { //get all groups return CapabilityLoader.GetCapabilities(); } public override string[] GetRolesForUser(string username) { //get all groups for a user string companyName; string userName; string[] items = username.Split('~'); companyName = items[1]; userName = items[0]; return ADCapabilities.GetUserCapabilities(companyName, userName).EnabledCapabilityList; } public override bool IsUserInRole(string username, string roleName) { //check if a user is in the group string companyName; string userName; string[] items = username.Split('~'); companyName = items[1]; userName = items[0]; return ADCapabilities.IsUserInAllGroups(companyName, userName, new string[] { roleName }); } } }

一般在ASP.NET的安全方案中,RoleProvider和MembershipProvider成对出现,而在我的WCF安全方案中,验证部分同样使用自定义MembershipProvider,下篇文章介绍MembershipProvider。
(部分文字根据Artech的指正做了修改)
Tags:  wcf之旅 我的wcf之旅

延伸阅读

最新评论

发表评论