Enterprise LibraryV1.0-安全应用程序块使用说明
目前的安全应用程序块实际上是解决权限管理问题的。
我不再对其源码进行解释了,有兴趣的朋友可以一起沟通。
1、权限管理概述
在我们的项目中基本上都会面对权限管理的问题,大家各有办法,虽然基本上能够满足要求,但与满意的解决这个问题都是有差距的,往往是某些需求没有很好的满足,勉强能用,不灵活,难以扩展。这里举个例子,比如在一个销售部门里有很多员工,他们都属于Employee权限组(Role),都有浏览订单、添加订单的权限,但没有审批订单的权限,有一个员工Johnny(张三)也属于Employee权限组,但要求他只能浏览订单浏览,没有其他权限,我们在代码中一般这样写:
If (user.Role in Employee && user.UserName == “Johnny”)
{
// 只能浏览
}
Else if(user.Role in Employee)
{
// 可以浏览、添加
}(代码一)(这个是伪代码,只是表明含义)
这时,需求发生了变化:Sunny原来具有Employee权限组的所有权限,但现在要求她只能浏览订单的,我们不得不求修改源代码,重新编译、重新发布程序(当然,就这个问题而言,再建一个Role让Johnny和Sunny属于这个Role即可)。更困难的是,如果原来是根据已有角色写代码的,当添加新的角色时,我们就不得不重新修改原来的编码逻辑,重新设计业务流程,重新编写源代码。这样的代码维护起来非常困难,也极不灵活,甚至不能完全满足用户需求。一种解决办法是,在判断是否属于某些角色时,这些角色来源于配置文件或数据库,而不是硬编码到程序中,同时对于权限判断中的个别的用户名也源于配置文件或数据库,这就带来了灵活性,实际上很多情况下我们都是这样做。但是这样做我们还是免不了要用这样带代码来写逻辑: if(user.Role in Roles && user.UserName in Users) ……这样做还是未解决权限判断的灵活性。因为当我执行一项操作(如浏览、添加、删除、审批)时,期待的权限判断应该是这样的:
If(user.IsAuthorized)
{
// 执行相应功能
}(代码二)
而不是象(代码一)那样,复杂而不灵活。实际上,当用户user登录以后,其对此项操作的IsAuthorized为true还是false就已经确定了,如果用代码二,当需求发生变化,登录用户的角色、权限改变时,我们的客户端代码不做任何修改,也就不用重新编译、重新发布了,只修改服务器端就行了。更加方便的是,如果我们将授权的规则写到配置文件或数据库,服务器端也不用修改,只是修改配置数据就行了。这样当需求发生变化时,我们的代码不做任何修改,修改配置就能应付变化了。这也是Martin Flower的plugin模式的具体应用。
实际上,象日志记录、异常处理、权限管理等都不属于业务逻辑,而是一些功能项的附属物,其最新解决办法是基于AOP的编程,但AOP远未成熟(再说我也就是只知道AOP这个名词,具体深层次的实现也不甚了了),难以商用,再说也可能还没有成熟就被其他技术所取代了,但对于权限管理的具体实现方式而言,有多种,其中基于角色的管理(RBAC)是最有效的,在2004年2月,RBAC被美国国家标准委员会(ANSI)和IT国际标准委员会(INCITS)接纳为ANSI INCITS 359-2004标准。其实,整个.Net Framework的权限设置就是根据RBAC设计的(猜的)。安全应用程序块在RBAC的应用上作了进一步努力,使其更加实用,安全应用程序块的权限设计思想并不仅限于.Net,它是一个权限管理解决方案,用其他语言(如java、vc)做项目时,完全可以参照它的设计思想。
现在的安全应用程序块主要完成权限管理的功能,我们在使用它之前要明白一个基本的处理过程,就是用户提供一个证书(一般是账号和密码的组合),由验证系统验证是否合法,如果合法,系统就提供一个通过验证的标志(IIdentity),这就完成了验证(实际上就是常见的登录过程)。根据用户这个IIdentity我们可以得到一个IPrincipal对象(实现IPrincipal接口的类的对象),即用户安全上下文,它包含了用户标识(IIdentity)和用户所属的角色(Roles)。同时对于每一项操作而言,我们都规定了其可以操作的角色(这里用一个Rule表示对一项功能的操作规则),当要执行这项操作之前,我们就拿此操作的Rule与IPrincipal进行比照,看看此IPrincipal是否满足这个Rule,如果满足说明有操作的权限,反之则没有。这样就达到了验证与授权的目的。
2、安全应用程序块简介
安全应用程序块提供一系列对象来完成上述认证、授权、角色管理的功能,同时还提供个性化设置功能。我们知道,完成认证、授权、角色管理等功能的方式有很多种,比如授权,我们可以根据win2k3提供的AzMan来进行授权,也可以根据配置文件中保存的Rule来授权,还可以根据数据库中保存的Rule来授权,又比如角色管理,我们可以根据数据库中保存的角色进行角色管理,也可以根据windows域里的角色来进行角色管理。具体用选哪一种,要根据项目的具体情况来选择,通过运用工厂模式,我们可以在不同功能提供者之间进行无缝切换,甚至在一个应用程序里综合运用多个功能提供者(当然,对于一项功能来说只能用一种功能提供者)。下面分别介绍。
①认证
提供认证机制的具体对象可以有多种,认证提供者工厂类根据当前的设置创建一个具体的认证提供者对象。这个认证提供者对象根据用户证书执行认证功能,从而产生一个IIdentity接口对象。用代码简单描述如下:
// 创建一个用户证书
NamePasswordCredential credentials = new NamePasswordCredential(username, password);
// 由认证提供者工厂创建一个认证提供者
IAuthenticationProvider authenProvider = AuthenticationFactory.GetAuthenticationProvider();
IIdentity identity;
// 认证提供者根据证书执行认证功能,从而得到一个用户是否通过验证的标志
authenticated = authenProvider.Authenticate(credentials, out identity);
②角色管理
当得到一个IIdentity实例(实现IIdentity接口的类的实例)后,我们就可以根据这个IIdentity实例得到用户的IPrincipal,这个IPrincipal是根据物理存储的数据得到的,一般是根据用户帐号去查询配置文件或数据库中这个账号的角色,最后实例化一个IPrincipal对象(实现IPrincipal接口的类的对象)。
// 角色管理工厂根据配置数据创建一个角色管理提供者
IRolesProvider rolesProvider = RolesFactory.GetRolesProvider();
// 角色管理器根据identity获取角色,并返回一个IPrincipal对象
IPrincipal principal = rolesProvider.GetRoles(identity);
// 将IPrincipal赋值给当前上下文。注意winform和webform的不同用法,不一定必须是这样用,也可以有别的用法
// 比如在webform里可以建立一个FormsAuthenticationTicket对象来处理
Thread.CurrentPrincipal = principal; // HttpContext.Current.User = principal;
(注意上述代码只是说明问题的代码,不一定能运行)
③授权
到底当前用户能不能执行某项操作是由授权管理部分确定的。判断是否有权执行某项操作之前,我们还要做一个基础性工作,就是对于每一项操作我们都要定义好哪些角色或用户能够执行,哪些角色或用户不能执行这样一个规则Rule,如何定义这个规则呢?Configuration Console提供了一个图形化的设计工具,很好设置,当然,你也可以自己手动创建这写的规则,也可以不用Configuration Console提供了一个图形化的设计工具,在自己的工程里实现一个。实际上,这个工具在我们大部分的项目中都能找到类似的实现,简单点说就是设置某项功能可以由哪些角色(和用户)执行。制定好Rule后具体保存到哪里由你自己决定,Rule工厂类屏蔽了这样的细节,对你的代码没有任何影响,不过好像大部分人都保存到数据库里(虽然Enterprise Library1.1没有实现将Rule保存到数据库里,但在Enterprise Library Communication可以找到)。当定义好Rule后,操作就和Rule一一对应了,你就可以查看IPrincipal是否有权执行某项操作了。代码非常简单:
//授权
public static bool Authorized(string rule)
{
bool authorized = false;
// 授权工厂根据配置创建一个授权提供者
IAuthorizationProvider ruleProvider = AuthorizationFactory.GetAuthorizationProvider();
// 查看当前用户是否有权限执行某项操作,这里的参数rule就是代表了某项操作的名字
authorized = ruleProvider.Authorize(Thread.CurrentPrincipal, rule);// asp.net:HttpContext.Current User
return authorized;
}
④个性化设置
Personalization也许对你的程序在实现业务逻辑是帮助不大,但如果运用适当它会使你的程序更眩,更人性化,更具有亲和力,从而迅速得到用户认可。其实Personalization设置有点像配置应用程序块,你先写一个Profile类来定义好你的个性化信息,这个类是可串行化的,当然如果你要对不同角色或用户应用不同的个性化设置,就要保存多条这样的记录。根据不同的角色或用户来提取其个性化Profile对象。与配置应用程序块一样,也是先设置才能读取。
设置:
ProfileInfo _profile = new ProfileInfo();
// 设置个性化信息
…….
// 由个性化配置信息提供者的工厂类根据配置数据建立一个个性化配置信息提供者
IProfileProvider profileProvider = profileProvider = ProfileFactory.GetProfileProvider();
// 将配置信息保存
profileProvider.SetProfile(Thread.CurrentPrincipal.Identity, _profile);
读取:
IProfileProvider profileProvider = ProfileFactory.GetProfileProvider();
ProfileInfo _profile = (ProfileInfo) profileProvider.GetProfile(Thread.CurrentPrincipal.Identity);
然后应用这个profile,设置个性化信息。
3、安全应用程序块使用步骤
下面介绍如何在我们的项目中使用安全应用程序块,其基本使用步骤是:
在数据库中建立相关的表和存储过程。
添加一些用户、角色、用户所属角色的数据。
设置程序每一项功能的操作规则。
用配置控制台设置安全应用程序块。
写操作代码。
在这里,一些大家都知道如何做的细节问题我们将不再赘述,如添加引用、添加using语句等等。在具体说明如何使用之前,我们先assume一个scenario,我们以先前ASP.NET Starter Kit里的一个程序IssueTracker为例来说明其基本使用步骤(如果不了解IssueTracker,请先了解一下)。
Users设置为Johnny、Bob、Sunny、Alice、Tom五人。
Roles设置为Administrator、Project Manager、Consultant、Developer、Guest、Architect、QA Tester七个角色。
Administrator具有全部功能,但主要起着维护系统的作用,实际并不参与系统的Business Logic。
Project Manager具有添加、修改、删除项目的权限,还有添加、修改、删除Issue的authority。
Developer具有添加、修改Issue的authority,无删除权限。
Guest仅有浏览的authority。
我们要先将这些假想数据,想办法保存进数据库(当然,也可以是其它persistent storage),下面我们详述使用步骤。
①建立几个表,如Users、Roles、UserRoles等,
②建立几个存储过程,如GetAllUsers、GetAllRoles等,如果操作的Rule保存到数据库中,你也要添加Rules表和GetAllRules存储过程。表的名字可以随便起,但存储过程的名字,已经写死在了代码中,最好不要修改了。
按照你的商业逻辑在Users、Roles、UserRoles表中保存一些数据,为了达到这个功能我们一般会自己做一些界面,让用户可以增删改用户和角色,同时制作设置用户角色的界面。EL也提供了这方面的一个工具:安全配置控制台,来达到维护这方面数据的目的。
(一些数据库设置方面的细节问题可以通过仔细查看SecurityDatabase.sql和UserRoleManager.cs这两个文件来解决。)
在这里我们用EL提供的安全配置控制台来设置数据,在具体项目中你也许用自己定制的
当我们选择好一个数据库后,点连接按钮,就会连接到指定的数据库,这里是IssueTracker,我们可以看到现在数据库里的Users和Roles什么没有。可以在左边栏中添加角色,在右边栏中添加用户,并为用户分配角色。按照我们上面的assume我们设置好数据。
设置完用户、角色以及用户所述角色之后,并没有结束,还要设置我们的系统有哪些操作,哪些角色和用户能执行这些操作,即操作权限。
在这里我们设置了几项操作,并决定让哪些角色和用户可执行,请注意Rule的写法要规范,同时工具还提供了校验功能。
设置完基础数据后,我们就可以正式开工了,先进行配置。
①打开配置控制台,添加Security AB,并开设置各项的值。这里我们以设置Authentication(登录管理)、Authoriztion(操作权限管理)、Roles(角色管理)三项为例来说明,这也是我们最常用的。
②如果没有添加数据库应用程序块和加密应用程序块,就添加二者,并设置好各个属性,加密应用程序块要添加一个HashAlgorithm Provider。
③在Authentication节点上添加一个Database AuthenticationProvider。设置好其Database和HashProvider。
④在Authorization节点上添加一个Database Authorization Provider。设置好其Database。
⑤设置角色的Provider,与设置Authentication和Authorization类似,不再赘述。
⑥设置Security AB的各个Provider,选定Security Application Block节点,设置好各个Default Provider。
⑦保存设置,完毕。
验证的代码逻辑为:
l 创建一个证书。
l 由设定的验证机制根据这个证书建立标识。
l 再由此标识建立主体对象,即安全上下文。
l 根据安全上下文建立票据,并放到Cookies中。
当客户端不启用Cookies时,虽然Session ID会放到来回trip的URL中,但Forms身份验证就不行了,所以当你选择了Forms身份验证,就意味着客户端Browser必须启用Cookie,否则你不能使用Forms身份验证。不过这是ASP.NET说了算的,听说新版本中,Forms身份验证可以选择以 Cookieless 方式工作。
验证代码的最终目的是建立一个IPrincipal对象和保存一个检索此Principal对象的票据(token或ticket),并使票据可恢复。
bool authenticated = false;
// 验证登录用户时添加的代码
NamePasswordCredential credentials = new NamePasswordCredential(txtUserName.Text, txtPassword.Text);
IAuthenticationProvider authenProvider = AuthenticationFactory.GetAuthenticationProvider();
IIdentity identity;
authenticated = authenProvider.Authenticate(credentials, out identity);
if(!authenticated)
{
lblPassword.Text = "";
return;
}
// 获取当前用户的角色列表
IRolesProvider rolesProvider = RolesFactory.GetRolesProvider();
IPrincipal principal = rolesProvider.GetRoles(identity);
// 放到缓存中
ISecurityCacheProvider securityCache = SecurityCacheFactory.GetSecurityCacheProvider();
IToken token = securityCache.SavePrincipal(principal);
// 建立票据
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
1,
txtUserName.Text,
System.DateTime.Now,
System.DateTime.Now.AddMinutes(30),
false,
token.Value,// 仅仅是principal的key
FormsAuthentication.FormsCookiePath);
// 加密登录票据.
string encryptedTicket = FormsAuthentication.Encrypt(ticket);
// 创建Cookie,添加到HttpCookieCollection.
Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket));
// 重新回到原来的URL.
Response.Redirect(FormsAuthentication.GetRedirectUrl(txtUserName.Text,true));
在验证时我们已经建立了IPrincipal对象,当判断权限时,我们首先要得到这个IPrincipal对象,我这里写了一个方法实现。
private IPrincipal GetPrincipal()
{
// 获取票据(这里仅仅是要得到principal的key)
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt((string)Request.Cookies[FormsAuthentication.FormsCookieName].Value);
GuidToken token = new GuidToken(new System.Guid(ticket.UserData));
// 从缓存中读取
ISecurityCacheProvider securityCache = SecurityCacheFactory.GetSecurityCacheProvider();
IPrincipal principal = securityCache.GetPrincipal(token);
if ( principal == null )
{
Response.Redirect("~/Login.aspx");
}
return principal;
}
一般我们会将此方法放到基类中,以避免重复写。也可以实现为基页的一个属性。注意这里并没有将Context.User或HttpContext.Current.User作为IPrincipal(实际上是GenericPrincipal对象)的驻留对象,所以你不能利用Page.User或(this.)User访问到此对象,但无论如何,不管是WinForm程序还是
bool authorized = false;
string rule = "Delete Issue";
IAuthorizationProvider ruleProvider = AuthorizationFactory.GetAuthorizationProvider();
authorized = ruleProvider.Authorize(GetPrincipal(), rule);
if(authorized)
{
// 允许执行操作
}
else
{
// 无权执行此项操作
}
这里写的这些代码千万不要Copy、Paste到你的程序中,而是要整合到你的程序的架构中,这里只是非常简单的介绍了如何使用之,可能非常不完善
⑺个性化设置
安全应用程序块还包含个性化设置功能,如果恰当应用此功能可以使您的程序更具人性化、界面更友好,使用时仿照配置应用程序块就可以了,我们就不在此详细介绍了。