所谓授权者,就是服务授予客户端是否具有调用某个服务操作的权限。
授权过程可以通过一系列授权策略来进行评估,即每个特定的授权策略都按照各自的需求,衡量一下调用方是否具备访问服务操作的权限。在默认情况下,服务的授权策略列表中,会存在一个 UnconditionalPolicy 授权策略,这个类型没有公开,它的定义如下:
- class UnconditionalPolicy: IAuthorizationPolicy,
- IDisposable {……
- }
正因为这家伙定义为 internal,故在我们的代码中是不能访问到的。这个类型是内部授权的评估过程,我们在开发中可以不理它,因为它在内部肯定会被使用的,你管不着它。
除了内部授权策略外,我们可以实现自己的授略策略类,以扩展 WCF 服务的授权功能。自定义授权策略的方法很简单,就是实现 IAuthorizationPolicy 接口。注意,使用这厮要引用 System.IdentityModel.dll 程序集,因为它是 WIF 的一部分。
由于 IAuthorizationPolicy 接口派生自 IAuthorizationComponent 接口,两个接口加起来,我们的自定义类必须实现以下三个成员:
1、Id——这个属性是字符串值,只读。它表示一个唯一值,以区别于其他授权相关的组件类,对于这个属性,最好的办法就是返回一个 GUID 值,这样就可以保证唯一性了。
2、Issuer——一个声明集,即 ClaimSet,它表示该授权策略的发布者。通常这个属性可以直接返回 System 或 Windows,这两个值都是 ClaimSet 类的静态成员。这样返回比较简单,你如果不希望使用系统默认声明集,也可以自己组装一个,一个声明集里面包含 N 个 Claim 对象,一个 Claim 表示一个声明。声明这玩意儿怎么解释呢。你就把它理解为一个符号吧,这种符号由 type、resource,right 三元素组成,type 是字符串,可以使用标准的类型,这些标准由 System.IdentityModel.Claims.ClaimTypes 类的静态属性公开,你可以直接用,比如 URI,E-mail,Name、国家(Country)、DNS 等。其实你看到了,声明类型有点像联系人资料的字段;除了标准值,你也可以自己定义值,反正是字符串,你可以随便,比如 city 表示城市,age 表示年龄,RP 表示人品值,等等。right 是权限,System.IdentityModel.Claims.Rights 类的静态属性公开两个标准值,同样,它也是字符串,所以你也可以定义非标准的值,比如 delete、add、new、save 等等,反正是个字符串就行。而 resource 的数据类型为 Object,即你可以引用任意值,但最好是可以序列化的实例,毕竟它最后是变成 XML 的,resource 是附加内容,可选。
3、Evaluate 方法——这个是核心,在这个方法里面你要对访问者进行评估。客户端经过服务的身份验证后,一般会产生一个 Identity 对象,这个标识是存放到一个字典数据中的,这个后面会给大伙说。注意这个方法返回的是布尔值,如果返回 false,那么,一旦授权上下文发生变化,比如添加了安全实体、其他标识,或者其他的授权策略也进行评估时,都会触发这个方法被调用。返回 true,表示此次评估一次性通过,后面如果授权上下文发生变化,也不再调用;如果返回 false,比较危险,有可能导致循环调用,一般来说,处理完成后,应该返回 true。
细节的东西我们先不管,我们先来学会如何自定义授权策略,并且把自定义的策略放进服务中。
下面咱们定义两个授权策略,这里只为了演示,所以 Evaluate 方法里面不做什么。
- class MyPolicyA: IAuthorizationPolicy {
- Guid mid = Guid.NewGuid();
- public string Id {
- get {
- return mid.ToString();
- }
- }
- public ClaimSet Issuer {
- get {
- return ClaimSet.System;
- }
- }
- public bool Evaluate(EvaluationContext evaluationContext, ref object state) {
- Console.WriteLine($ "进入 {nameof(MyPolicyA)} 的 {nameof(Evaluate)} 方法");
- return false;
- }
- }
- class MyPolicyB: IAuthorizationPolicy {
- Guid id = Guid.NewGuid();
- public string Id {
- get {
- return id.ToString();
- }
- }
- public ClaimSet Issuer {
- get {
- return ClaimSet.System;
- }
- }
- public bool Evaluate(EvaluationContext evaluationContext, ref object state) {
- Console.WriteLine($ "进入 {nameof(MyPolicyB)} 的 {nameof(Evaluate)} 方法。");
- return false;
- }
- }
这个看得懂吧,在 Evaluate 方法中加入了 Console 的输出,为了在运行时知道方法被执行,这里呢,我统一返回 false。
然后,看看如何把自定义策略添加到服务中,ServiceHost 类会向服务的 Behavior 列表添加一个 ServiceAuthorizationBehavior,并且为了便于访问,还直接以 Authorization 属性公开。下面把刚刚定义的两个策略加入到服务中。
- List authorlist = new List();
- authorlist.Add(new Coc.MyPolicyA());
- authorlist.Add(new Coc.MyPolicyB());
- host.Authorization.ExternalAuthorizationPolicies = authorlist.AsReadOnly();
一定要注意,身份验证和授权的两个单词很像,授权是 Authorization 单词。
如果你不想用代码来添加自定义授权策略,也可以用配置文件来完成。
- <behaviors>
- <serviceBehaviors>
- <behavior>
- <serviceAuthorization>
- <authorizationPolicies>
- <clear/>
- <add policyType="Coc.MyPolicyA, SomeApp771" />
- <add policyType="Coc.MyPolicyB, SomeApp771" />
- </authorizationPolicies>
- </serviceAuthorization>
- </behavior>
- </serviceBehaviors>
- </behaviors>
代码方式和配置文件方式,任选其一即可,不要两种都同时用上,记住!!在配置文件中,policyType 除了指定自定义策略类的路径外,还得写上你的程序集的名字,上面例子中的 SomeApp771 就是程序集的名字,不然的话,运行时会跑到 System.ServiceModel 程序集里面去找,结果就会找不到类型而发生异常。
好,现在可以运行一下,试试看。
你都看到,每个 Evaluate 方法都被调用了两次,那是因为我们刚才让它返回 false。
接下来,咱们把上面两个策略中的 Evaluate 方法都改一下,让它们都返回 true。
- public bool Evaluate(EvaluationContext evaluationContext, ref object state) {
- Console.WriteLine($ "进入 {nameof(MyPolicyA)} 的 {nameof(Evaluate)} 方法");
- return true;
- }
然后再运行,就会发现,方法只被执行了一次。
如果 Evaluate 方法返回 true,那么后面就算授权上下文发生变化,也不再调用方法。
相信各位都注意到了,Evaluate 方法有个 EvaluationContext 类型的参数,它是个抽象类,运行内部有实现类,但未对外公开,我们可以通过 EvaluationContext 实例来访问它,既然叫 Context(上下文),因而它的实例在所有授权策略的评估过程中,都是共享同一个实例,故这个上下文实例会在各个策略中传递。
在 Evaluate 方法中,可以向 EvaluationContext 参数添加自定义声明集,即前面说过的 ClaimSet,而声明集的标识是当前授权策略的实例,添加声明应调用以下方法:
- void AddClaimSet(IAuthorizationPolicy policy, ClaimSet claimSet);
第一个参数就是当前策略实例。
下面给大伙演示一个授权策略评估示例,并向声明集列表中添加自定义声明。
- class CustPolicy: IAuthorizationPolicy {
- Guid id = Guid.NewGuid();
- public string Id {
- get {
- return id.ToString();
- }
- }
- public ClaimSet Issuer {
- get {
- return ClaimSet.System;
- }
- }
public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
Console.WriteLine($" 进入 {nameof(Evaluate)} 方法。");
// 声明列表
List
clist.Add(new Claim("city"," 广州 ", Rights.Identity));
clist.Add(new Claim(ClaimTypes.Email,"sunset@163.com","access"));
// 实例化声明集
ClaimSet clset = new DefaultClaimSet(clist);
// 添加到上下文
evaluationContext.AddClaimSet(this, clset);
return false;
}
- }
声明集中放了两个声明,第一个的 type 用的自定义值 city,resource 是字符串 "广州",right 用的是标准值;第二个声明的 type 用的是标准值,right 是自定义值 access。
然后,把它放到服务中。
- <serviceBehaviors>
- <behavior>
- <serviceAuthorization>
- <authorizationPolicies>
- <clear/>
- <add policyType="Coc.CustPolicy, SomeApp771" />
- </authorizationPolicies>
- </serviceAuthorization>
- </behavior>
- </serviceBehaviors>
由于 Evaluate 方法返回的是 false,因此在运行后,你会看到以下恐怖的一幕。
EvaluationContext 类公开了一个属性,叫 Generation, 它是一个整数值,只要 AddClaimSet 方法被调用一次,Generation 的值就会加 1,可以看看. NET 的源代码。
- public override void AddClaimSet(IAuthorizationPolicy policy, ClaimSet claimSet) {……
- if (this.claimSets == null) this.claimSets = new List();
- this.claimSets.Add(claimSet); ++this.generation;
- }
注意看最后一行,看到没,generation 被 ++ 了,所以,每添加一次声明集,Generation 就会增加 1。
不信的话,可以把 Evaluate 方法做以下修改。
- public bool Evaluate(EvaluationContext evaluationContext, ref object state) {……Console.WriteLine($ "Generation = {evaluationContext.Generation}");
- return false;
- }
然后再运行,看看输出。
这个调用会一直循环,直到发生异常。为啥,因为在 Evaluate 方法中我们添加了声明集,而这个行为会导致 Generation 的值增加,从而使所有授权策略的 Evaluate 方法又被调用,如此不断循环下去。
但是,如果让 Evaluate 方法返回 true,就不再死循环了,比如:
- public bool Evaluate(EvaluationContext evaluationContext, ref object state) {……
- //return false;
- return true;
- }
好了,现在只调用一次就行了。
所以啊,只要评估通过,就应该返回 true。
那么,为什么返回 false 会导致无限循环呢,我们再看. NET 源代码,其中这么一段:
- object[] policyState = new object[authorizationPolicies.Count];
- object done = new object();
- int oldContextCount;
- do {
- oldContextCount = evaluationContext.Generation;
- for (int i = 0) {
- if (policyState[i] == done) continue;
- IAuthorizationPolicy policy = authorizationPolicies[i];
- if (policy == null) {
- policyState[i] = done;
- continue;
- }
- if (policy.Evaluate(evaluationContext, ref policyState[i])) {
- policyState[i] = done;
- if (DiagnosticUtility.ShouldTraceVerbose) {
- TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.AuthorizationPolicyEvaluated, SR.GetString(SR.AuthorizationPolicyEvaluated, policy.Id));
- }
- }
- }
- } while ( oldContextCount < evaluationContext . Generation );
这段代码,你看不看得懂无所谓,重点是看里面的两套循环。最外层循环是个 do...while,循环的退出条件是:evaluationContext 的 Generation 属性的值不再增加,只要不 ++ 那就退出循环。刚刚我们说过,每调用一次 AddClaimSet 方法,generation 的值就会 ++ 一回,所谓 generation 不再 ++,则表明 AddClaimSet 方法不再调用,而使它不被调用的方法是让 Evaluate 方法不被调用,只要 Evaluate 方法返回 true 就不会再被调用。
为什么呢,咱们看里面的 for 循环,如果 Evaluate 方法返回 true,则让 state 的值设为 done。
- if (policy.Evaluate(evaluationContext, ref policyState[i])) {
- policyState[i] = done;…………
而在 for 循环的每一轮执行中,先判断一下 state 是否为 done,如果 done 了,就用 continue; 跳过。
- for (int i = 0) {
- if (policyState[i] == done) continue;…………
在 for 循环里面只要一 continue,那么此轮循环就跳过了,这样后面的代码就不会执行,那么 Evaluate 方法就不再调用了。
正因为如此,在完成评估后,一定要让 Evaluate 方法返回 true。
所谓自定义授权管理器,就是从 ServiceAuthorizationManager 类派生出一个类,并重写其 CheckAccessCore 方法,如果检查通过,允许调用者访问服务操作,就返回 true,表示通过,如果不通过就返回 false。
为什么要重写这个方法呢,你看看. NET 源代码就明白了:
- protected virtual bool CheckAccessCore(OperationContext operationContext) {
- return true;
- }
直接返回 true,万能授权。
授权检查是以服务操作为单位的,即 Operation,因为服务的每次调用,实际上你只能调用一个操作方法,因为 WCF 是基于消息的,跟 HTTP 一样,一问一答(当然也可以只问不答,比如单工模式),故授权是基于操作为单位的,CheckAccessCore 方法就一个参数,OperationContext,这个东东是跟当前要调用的操作相关的信息。
下面我们来实现一下。
- class MyAuthorManager: ServiceAuthorizationManager {
- protected override bool CheckAccessCore(OperationContext operationContext) {
- ServiceSecurityContext sccontext = operationContext.ServiceSecurityContext;
- // 检查声明集
- foreach(ClaimSet clset in sccontext.AuthorizationContext.ClaimSets) {
- foreach(Claim c in clset.FindClaims("city", Rights.Identity)) {
- if (c.Resource.ToString() == "佛山") {
- return true;
- }
- }
- }
- return false;
- }
- }
其实这个授权检查会失败的,还记得吗,我们刚刚在自定义授权策略时,在 Evaluate 方法中,添加的声明集里面,其中有一个 type 为 city,resource 为 "广州",但是,你看看此处,我检测的值是 "佛山",所以,这个授权检测会返回 false,表示授权不通过,想调用服务,没门!
接着,不要忘了,把刚定义的授权管理器类添加到授权的 ServceBehavior 中,下面是配置文件写法:
- <serviceAuthorization serviceAuthorizationManagerType="Coc.MyAuthorManager, SomeApp771">
- <authorizationPolicies>
- <clear/>
- <add policyType="Coc.CustPolicy, SomeApp771" />
- </authorizationPolicies>
- </serviceAuthorization>
实际上,就是指定一下 serviceAuthorizationManagerType 的值就可以了,注意要写上程序集名称。
好,现在,调用一下服务,你就会收到一个异常——"拒绝访问".
然后,咱们把刚才的 MyAuthorManager 类的代码,把判断值 "佛山" 改为 "广州"。
- foreach(Claim c in clset.FindClaims("city", Rights.Identity)) {
- if (c.Resource.ToString() == "广州") {
- return true;
- }
- }
检测成功,返回 true,所以此时服务就能正常调用了。
哟,今天给大伙讲的这个玩意儿有点不好理解,各位得有心理准备,但是,该克服的困难就应当克服,不要逃避。
来源: http://www.cnblogs.com/tcjiaan/p/5834389.html