【WCF】授权策略详解

所谓授权者,就是服务授予客户端是否具有调用某个服务操作的权限。

授权过程可以通过一系列授权策略来进行评估,即每个特定的授权策略都按照各自的需求,衡量一下调用方是否具备访问服务操作的权限。在默认情况下,服务的授权策略列表中,会存在一个UnconditionalPolicy授权策略,这个类型没有公开,它的定义如下:

    class UnconditionalPolicy : IAuthorizationPolicy, IDisposable
    {
          ……
    }

正因为这家伙定义为internal,故在我们的代码中是不能访问到的。这个类型是内部授权的评估过程,我们在开发中可以不理它,因为它在内部肯定会被使用的,你管不着它。

除了内部授权策略外,我们可以实现自己的授略策略类,以扩展WCF服务的授权功能。自定义授权策略的方法很简单,就是实现 IAuthorizationPolicy 接口。注意,使用这厮要引用 System.IdentityModel.dll 程序集,因为它是WIF的一部分。

IAuthorizationPolicy 的 Evaluate 方法

由于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<IAuthorizationPolicy> authorlist = new List<IAuthorizationPolicy>();
            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<Claim> clist = new List<Claim>();
            clist.Add(new Claim("city", "广州", Rights.Identity));
            clist.Add(new Claim(ClaimTypes.Email, "[email protected]", "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<ClaimSet>();

            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; i < authorizationPolicies.Count; i++)
                    {
                        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; i < authorizationPolicies.Count; i++)
                    {
                        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,所以此时服务就能正常调用了。

哟,今天给大伙讲的这个玩意儿有点不好理解,各位得有心理准备,但是,该克服的困难就应当克服,不要逃避。

示例源代码下载地址

时间: 2024-08-08 20:45:59

【WCF】授权策略详解的相关文章

Android 开源框架Universal-Image-Loader完全解析(二)--- 图片缓存策略详解

本篇文章继续为大家介绍Universal-Image-Loader这个开源的图片加载框架,介绍的是图片缓存策略方面的,如果大家对这个开源框架的使用还不了解,大家可以看看我之前写的一篇文章Android 开源框架Universal-Image-Loader完全解析(一)--- 基本介绍及使用,我们一般去加载大量的图片的时候,都会做缓存策略,缓存又分为内存缓存和硬盘缓存,我之前也写了几篇异步加载大量图片的文章,使用的内存缓存是LruCache这个类,LRU是Least Recently Used 近

七牛云存储Python SDK使用教程 - 上传策略详解

文 七牛云存储Python SDK使用教程 - 上传策略详解 七牛云存储 python-sdk 七牛云存储教程 jemygraw 2015年01月04日发布 推荐 1 推荐 收藏 2 收藏,2.7k 浏览 本教程旨在介绍如何使用七牛的Python SDK来快速地进行文件上传,下载,处理,管理等工作. 前言 我们在上面的两节中了解到,客户端上传文件时,需要从业务服务器申请一个上传凭证(Upload Token),而这个上传凭证是业务服务器根据上传策略(PutPolicy)来生成的,而这个生成过程中

(转)OAuth 2.0授权协议详解和流程

这篇文章主要介绍了OAuth 2.0授权协议详解,本文对OAuth协议做了详解讲解,对OAuth协议的各个方面做了分解,读完本文你就会知道到底啥是OAuth了,需要的朋友可以参考下 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版.本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749. 一.应用场景 为了理解OAuth的适用场合,让我举一个假设的例子.有一个"云冲印"的网站,可

Spring AMQP 错误处理策略详解

1.介绍 异步消息传递是一种松耦合的分布式通信,在事件驱动体系结构实现中越来越受欢迎.幸运的是,Spring框架提供了Spring AMQP项目,可以帮助我们构建基于AMQP的消息传递解决方案. 另一方面,在这种环境中处理错误并不简单.本文将讨论错误处理策略. 2.配置环境 这里使用RabbitMQ实现AMQP标准.此外,Spring AMQP还提供了spring-rabbit模块,让集成更容易. RabbitMQ作为独立服务器运行.执行下面命令,在Docker容器中运行: docker run

guava-retrying 源码解析(停止策略详解)

一.停止策略相关类 1.停止策略接口:StopStrategy接口,只有一个抽象方法 // 是否应该停止重试.不同的停止策略有不同的实现.boolean shouldStop(Attempt failedAttempt); 2.停止策略工厂类:StopStrategies类 这是一个常量类.工厂类,用于创建停止策略对象.这个工厂类里面定义了三种停止策略,都是常量静态内部类. 该工厂类是创建停止策略的唯一途径. 二.详解三种停止策略 1.从不停止策略:NeverStopStrategy 使用这种策

guava-retrying 源码解析(阻塞策略详解)

这是一种策略,用于决定重试者应如何在重试尝试之间进行阻止.通常这只是一个thread.sleep(),但是如果需要的话,实现可能更复杂. 一.阻塞策略相关的类或接口 1.阻塞策略接口:BlockStrategy 底层默认使用来 Thread.sleep 完成线程阻塞,从而实现重试之间的等待{@link com.github.rholder.retry.WaitStrategy}.如果需要,实现可以更加复杂. 接口里面有一个实现方法,如下. 2.阻塞策略工厂类:BlockStrategies. 该

微信小程序 授权登录详解(附完整源码)

一.前言 由于微信官方修改了 getUserInfo 接口,所以现在无法实现一进入微信小程序就弹出授权窗口,只能通过 button 去触发. 官方连接:https://developers.weixin.qq.com/community/develop/doc/0000a26e1aca6012e896a517556c01 二.实现思路 自己写一个微信授权登录页面让用户实现点击的功能,也就是实现了通过 button 组件去触发 getUserInof 接口.在用户进入微信小程序的时候,判断用户是否

Linux系统备份策略详解

由于linux系统的特殊性,获取root用户权限后,很容易把系统搞崩溃,所以系统备份是一件不容忽视的大事.得益于linux系统自身的优越性,所以系统的备份和还原操作还是相对简单的. Linux系统所有的数据都以文件的形式存在,所以备份就是直接拷贝文件;硬盘分区也被当成文件,所以可以直接克隆硬盘数据. Linux系统自带很多实用工具,比如tar.dd.rsync等,备份还原系统不需要购买或下载第三方软件. Linux系统在运行时其硬盘上的文件可以直接被覆盖,所以还原系统的时候不需要另外的引导盘.

远程连接mysql 授权方法详解

今在服务器上有mysql数据库,远程访问,不想公布root账户,所以,创建了demo账户,允许demo账户在任何地方都能访问mysql数据库中shandong库,接下来为您详细介绍 今在服务器上 有mysql 数据库,远程访问,不想公布root账户,所以,创建了demo账户,允许demo账户在任何地方都能访问mysql数据库中shandong库. 方案一: 在安装mysql的机器上运行: 1: 创建user用户 复制代码 代码如下: CREATE USER demo IDENTIFIED BY