最近在弄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方式,因为这样对代码的侵入性要小,而且方式比较干净。
 
下面提供一个示例,加以说明。
在服务端的配置中,只要做如下配置:
<serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="MyProvider" />
这就表明服务端将采用RoleProvider的方式进行授权检查。
由于授权依赖于客户端传入用户名及密码,所以,ClientCredentialType可以选择Windows和UserName两种方式,其中,UserName可以支持其他的数据格式,例如你们产品的权限验证不仅仅依赖于用户名和密码,还有公司名,那么你可以把公司名和用户名都放在UserName中,在服务端再做拆解。而且UserName还支持MembershipProvider方式的验证以及其他自定义方式的验证,因此,在这里你可以选择UserName的方式。由于UserName的方式依赖于安全的消息传输,所以安全模式可以选择TransportWithMessageCredential。
这种方式的配置分别如下:
Binding:
<binding name="wsHttp1">
    <security mode="TransportWithMessageCredential">
        <transport clientCredentialType="None" realm="" />
        <message clientCredentialType="UserName" />
    </security>
</binding>
 
Service behavior:
<behavior name="securityBehavior">
    <serviceCredentials>
        <userNameAuthentication  userNamePasswordValidationMode="MembershipProvider"  membershipProviderName="MyMembershipProvider"/>
    </serviceCredentials>
    <serviceAuthorization principalPermissionMode="UseAspNetRoles" roleProviderName="MyProvider" />
</behavior>
(验证部分可以忽略)
Service:
<service name="TestService5.TestService" behaviorConfiguration="securityBehavior">
      <endpoint address="" 
                  binding="wsHttpBinding" 
                  bindingConfiguration ="wsHttp1" 
                  contract ="TestBase.ITestService2" 
                  name ="testService5" />
      <endpoint address="mex" 
                  binding="mexHttpsBinding" 
                  contract="IMetadataExchange" />
</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的指正做了修改)

作者: Bright Zhang 发表于 2011-05-25 03:14 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"