[WCF权限控制]WCF自定义授权体系详解[实例篇]

在《原理篇》中,我们谈到WCF自定义授权体系具有两个核心的组件:AuthorizationPolicy和ServiceAuthorizationManager,已经它们是如何写作最终提供一种基于声明的授权实现。为了让自定义授权有深刻的理解,我们来进行一个简单实例来演示如何通过自定义这两个组件实现“非角色授权策略”。[源代码从这里下载]

目录: 一、创建演示程序解决方案 二、自定义AuthorizationPolicy 三、自定义ServiceAuthorizationManager 四、应用自定义AuthorizationPolicy和ServiceAuthorizationManager

一、创建演示程序解决方案

。我们这个实例依然采用简单的计算服务的例子,并且采用如下图所示的解决方案结构。不过,为了后续的授权策略需要,我们在服务契约ICalculator接口上定义如下四个分别表示加、减、乘、除的四个运算操作。当然服务类型CalculatorService也进行相应的修正。

ICalculator:

   1: using System.ServiceModel;
   2: namespace Artech.WcfServices.Contracts
   3: {
   4:     [ServiceContract(Namespace = "http://www.artech.com/")]
   5:     public interface ICalculator
   6:     {
   7:         [OperationContract(Action = "http://www.artech.com/calculator/add")]
   8:         double Add(double x, double y);
   9:         [OperationContract(Action = "http://www.artech.com/calculator/subtract")]
  10:         double Subtract(double x, double y);
  11:         [OperationContract(Action = "http://www.artech.com/calculator/multiply")]
  12:         double Multiply(double x, double y);
  13:         [OperationContract(Action = "http://www.artech.com/calculator/divide")]
  14:         double Divide(double x, double y);
  15:     }
  16: }

CalculatorService:

   1: using Artech.WcfServices.Contracts;
   2: namespace Artech.WcfServices.Services
   3: {
   4:     public class CalculatorService : ICalculator
   5:     {
   6:         public double Add(double x, double y)
   7:         {           
   8:             return x + y;
   9:         }
  10:         public double Subtract(double x, double y)
  11:         {
  12:             return x - y;
  13:         }
  14:         public double Multiply(double x, double y)
  15:         {
  16:             return x * y;
  17:         }
  18:         public double Divide(double x, double y)
  19:         {
  20:             return x / y;
  21:         }
  22:     }
  23: } 

现在我们的授权策略是这样的:操作Add和Subtract针对仅对用户Foo开放,而Multiply和Divide操作仅对用户Bar开放。虽然这个简单的授权完全可以通过在相应的服务操作方法上应用PrincipalPermissionAttribute并指定Name属性来实现。但是我们要尝试通过自定义AuthorizationPolicy和ServiceAuthorizationManager来实现这样的授权策略。先来看看自定义的AuthorizationPolicy的定义。

二、自定义AuthorizationPolicy

我们将自定义的AuthorizationPolicy创建在Hosting项目中。由于IAuthorizationPolicy定义在System.IdentityModel程序集中,我们先为Hosting项目添加该程序集的引用。由于授权策略比较简单,我们直接上自定义的AuthorizationPolicy命名为SimpleAuthorizationPolicy,下面是整个SimpleAuthorizationPolicy的定义。

   1: using System;
   2: using System.Collections.Generic;
   3: using System.IdentityModel.Claims;
   4: using System.IdentityModel.Policy;
   5: using System.Linq;
   6: namespace Artech.WcfServices.Hosting
   7: {
   8:     public class SimpleAuthorizationPolicy : IAuthorizationPolicy
   9:     {
  10:         private const string ActionOfAdd = "http://www.artech.com/calculator/add";
  11:         private const string ActionOfSubtract = "http://www.artech.com/calculator/subtract";
  12:         private const string ActionOfMultiply = "http://www.artech.com/calculator/multiply";
  13:         private const string ActionOfDivide = "http://www.artech.com/calculator/divide";
  14:  
  15:         internal const string ClaimType4AllowedOperation = "http://www.artech.com/allowed";
  16:  
  17:         public SimpleAuthorizationPolicy()
  18:         {
  19:             this.Id = Guid.NewGuid().ToString();
  20:         }
  21:         public bool Evaluate(EvaluationContext evaluationContext, ref object state)
  22:         {
  23:             if (null == state)
  24:             {
  25:                 state = false;
  26:             }
  27:             bool hasAddedClaims = (bool)state;
  28:             if (hasAddedClaims)
  29:             {
  30:                 return true; ;
  31:             }
  32:             IList<Claim> claims = new List<Claim>();
  33:             foreach (ClaimSet claimSet in evaluationContext.ClaimSets)
  34:             {
  35:                 foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
  36:                 {
  37:                     string userName = (string)claim.Resource;
  38:                     if (userName.Contains('\\'))
  39:                     {
  40:                         userName = userName.Split('\\')[1];
  41:                         if (string.Compare("Foo", userName, true) == 0)
  42:                         {
  43:                             claims.Add(new Claim(ClaimType4AllowedOperation,ActionOfAdd, Rights.PossessProperty));
  44:                             claims.Add(new Claim(ClaimType4AllowedOperation, ActionOfSubtract, Rights.PossessProperty));
  45:                         }
  46:                         if (string.Compare("Bar", userName, true) == 0)
  47:                         {
  48:                             claims.Add(new Claim(ClaimType4AllowedOperation,ActionOfMultiply, Rights.PossessProperty));
  49:                             claims.Add(new Claim(ClaimType4AllowedOperation, ActionOfDivide, Rights.PossessProperty));
  50:                         }
  51:                     }
  52:                 }
  53:             }
  54:             evaluationContext.AddClaimSet(this, new DefaultClaimSet(this.Issuer, claims));
  55:             state = true;
  56:             return true;           
  57:         }
  58:         public ClaimSet Issuer
  59:         {
  60:             get { return ClaimSet.System; }
  61:         }
  62:         public string Id { get; private set; }
  63:     }
  64: }

我们主要来介绍Evaluate方法中,该方法主要的逻辑是这样的:通过EvaluationContext现有的声明集获取当前的用户名(声明类型和声明权限分别为ClaimTypes.Name和Rights.PossessProperty)。针对获取出来的用户名,创建于被授权服务操作关联的声明。其中声明的三要素(类型、权限和资源)分别为:“http://www.artech.com/allowed”、Rights.PossessProperty和操作的Action。最后将这些声明组成一个声明集添加到EvaluationContext中。

三、自定义ServiceAuthorizationManager

当授权相关的声明集通过自定义的AuthorizationPolicy被初始化之后,我们通过自定义ServiceAuthorizationManager来分析这些声明,并作做出当前操作是否被授权调用的最终判断。类似于SimpleAuthorizationPolicy,我们将自定义的ServiceAuthorizationManager起名为SimpleServiceAuthorizationManager,同样定义于Hosting项目中,下面是整个SimpleServiceAuthorizationManager类型的定义。

   1: using System.IdentityModel.Claims;
   2: using System.Security.Principal;
   3: using System.ServiceModel;
   4: namespace Artech.WcfServices.Hosting
   5: {
   6:     public class SimpleServiceAuthorizationManager : ServiceAuthorizationManager
   7:     {
   8:         protected override bool CheckAccessCore(OperationContext operationContext)
   9:         {
  10:             string action = operationContext.RequestContext.RequestMessage.Headers.Action;
  11:             foreach (ClaimSet claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
  12:             {
  13:                 if (claimSet.Issuer == ClaimSet.System)
  14:                 {
  15:                     foreach (Claim c in claimSet.FindClaims(SimpleAuthorizationPolicy.ClaimType4AllowedOperation, Rights.PossessProperty))
  16:                     {
  17:                         if (action == c.Resource.ToString())
  18:                         {
  19:                             GenericIdentity identity = new GenericIdentity("");
  20:                             operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] =
  21:                                 new GenericPrincipal(identity, null);
  22:                             return true;
  23:                         }
  24:                     }
  25:                 }
  26:             }
  27:             return false;            
  28:         }
  29:     }
  30: }

由于基于被授权操作的声明已经通过SimpleAuthorizationPolicy被成功添加到EvaluationContext的声明集列表,并最终作为当前AuthorizationContext声明集的一部分。那么,如果在这些代表被授权操作的声明中,具有一个是基于当前被调用的服务操作的声明,就意味着当前的服务操作调用被授权了的。这样的逻辑实现在重写的CheckAccessCore方法中。此外,还有一点需要注意的是:在做出成功授权的情况下,需要设置当前的安全主体,因为不管这个安全主体是否需要,WCF总是会试图从当前AuthorizationContext的属性列表中去获取该安全主体。如果没有,会抛出异常。

四、应用自定义AuthorizationPolicy和ServiceAuthorizationManager

到目前为止,两个核心的自定义对象(SimpleAuthorizationPolicy和SimpleServiceAuthorizationManager)都已经创建好了,我们现在通过配置的方式将它们设置到应用到服务的ServiceAuthorizationBehavior服务行为上。下面两段XML片断分别表示服务寄宿和客户端的配置。

服务寄宿配置:

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <services>
   5:       <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="useCustomAuthorization">
   6:         <endpoint address="http://127.0.0.1/calculatorservice"  binding="ws2007HttpBinding"                         contract="Artech.WcfServices.Contracts.ICalculator"/>
   7:       </service>
   8:     </services>
   9:     <behaviors>
  10:       <serviceBehaviors>
  11:         <behavior  name="useCustomAuthorization">         
  12:           <serviceAuthorization principalPermissionMode="Custom"                    serviceAuthorizationManagerType="Artech.WcfServices.Hosting.SimpleServiceAuthorizationManager,Artech.WcfServices.Hosting">
  13:             <authorizationPolicies >
  14:               <add policyType="Artech.WcfServices.Hosting.SimpleAuthorizationPolicy, Artech.WcfServices.Hosting" />
  15:             </authorizationPolicies>
  16:           </serviceAuthorization>
  17:           <serviceDebug includeExceptionDetailInFaults="true"/>
  18:         </behavior>
  19:       </serviceBehaviors>
  20:     </behaviors>
  21:   </system.serviceModel>
  22: </configuration>

客户端配置:

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <system.serviceModel>
   4:     <client>
   5:       <endpoint name="calculatorService" address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"                       contract="Artech.WcfServices.Contracts.ICalculator"/>
   6:     </client>  
   7:   </system.serviceModel>
   8: </configuration>

我们最终需要验证的WCF是否能够按照我们自定义的策略进行授权。为了演示方便,我创建了如下一个名称为Invoke的辅助方法。Invoke方法的三个参数分别代表进行服务调用的委托、服务代理对象和操作名称。服务操作调用会在该方法中执行,并最终输出相应的文字表示服务调用是否成功。

   1: static void Invoke(Action<ICalculator> action, ICalculator proxy, string operation)
   2: {
   3:     try
   4:     {
   5:         action(proxy);
   6:         Console.WriteLine("服务操作\"{0}\"调用成功...", operation);
   7:     }
   8:     catch (Exception ex)
   9:     {
  10:         Console.WriteLine("服务操作\"{0}\"调用失败...", operation);
  11:     }
  12: }

在如下的代码中,我们分别以用户名Foo和Bar的名义通过上面的Invoke辅助方法对计算服务的四个操作进行访问。而程序执行的最终结果是和我们自定义的授权策略是一致的:用户Foo仅仅授予了调用Add和Substract操作的权限,而其余两个授权给用户Bar。

   1: static void Main(string[] args)
   2: {
   3:     ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
   4:     NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
   5:     credential.UserName = "Foo";
   6:     credential.Password = "Password";
   7:     ICalculator calculator = channelFactory.CreateChannel();
   8:     Invoke(proxy => proxy.Add(1, 2), calculator, "Add");
   9:     Invoke(proxy => proxy.Subtract(1, 2), calculator, "Subtract");
  10:     Invoke(proxy => proxy.Multiply(1, 2), calculator, "Multiply");
  11:     Invoke(proxy => proxy.Divide(1, 2), calculator, "Divide");
  12:     Console.WriteLine();
  13:  
  14:     channelFactory = new ChannelFactory<ICalculator>("calculatorService");
  15:     credential = channelFactory.Credentials.Windows.ClientCredential;
  16:     credential.UserName = "Bar";
  17:     credential.Password = "Password";
  18:     calculator = channelFactory.CreateChannel();
  19:     Invoke(proxy => proxy.Add(1, 2), calculator, "Add");
  20:     Invoke(proxy => proxy.Subtract(1, 2), calculator, "Subtract");
  21:     Invoke(proxy => proxy.Multiply(1, 2), calculator, "Multiply");
  22:     Invoke(proxy => proxy.Divide(1, 2), calculator, "Divide");
  23:  
  24:     Console.Read();
  25: }

输出结果:

   1: 服务操作"Add"调用成功...
   2: 服务操作"Subtract"调用成功...
   3: 服务操作"Multiply"调用失败...
   4: 服务操作"Divide"调用失败...
   5:  
   6: 服务操作"Add"调用失败...
   7: 服务操作"Subtract"调用失败...
   8: 服务操作"Multiply"调用成功...
   9: 服务操作"Divide"调用成功...

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Core Net

ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事

35550
来自专栏Core Net

ASP.NET Core 2.0 : 八.图说管道,唐僧扫塔的故事

22740
来自专栏大内老A

WCF技术剖析之六:为什么在基于ASP.NET应用寄宿(Hosting)下配置的BaseAddress无效

本篇文章来源于几天前一个朋友向我咨询的问题。问题是这样的,他说他采用ASP.NET应用程序的方式对定义的WCF服务进行寄宿(Hosting),并使用配置的方式对...

20070
来自专栏大内老A

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇]

在《上篇》中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭。对于《WCF技术剖析(卷1)》的读者,应该会知道在第7章中我通...

20950
来自专栏大内老A

了解ASP.NET MVC几种ActionResult的本质:EmptyResult & ContentResult

在之前的两篇文章(《EmptyResult & ContentResult》和《FileResult》)我们剖析了EmptyResult、ContentResu...

29850
来自专栏游戏开发那些事

【游戏开发】Excel表格批量转换成lua的转表工具

  在上篇博客《【游戏开发】Excel表格批量转换成CSV的小工具》 中,我们介绍了如何将策划提供的Excel表格转换为轻便的CSV文件供开发人员使用。实际在U...

28230
来自专栏大内老A

通过“四大行为”对WCF的扩展[实例篇]

为了让读者对如何利用相应的行为对WCF进行扩展有个深刻的认识,在这里我提供一个简单的实例演示。本实例模拟的场景是这样的:我们创建一个支持多语言的资源服务,该服务...

20180
来自专栏木宛城主

迁移TFS,批量将文档导入SharePoint 2013 文档库

一、需求分析 公司需要将存在于旧系统(TFS)所有的文档迁移至新系统(SharePoint 2013)。现已经将50G以上的文档拷贝到SharePoint ...

224100
来自专栏MasiMaro 的技术博文

Vista 及后续版本的新线程池

在上一篇的博文中,说了下老版本的线程池,在Vista之后,微软重新设计了一套线程池机制,并引入一组新的线程池API,新版线程池相对于老版本的来说,它的可控性更高...

16630
来自专栏大内老A

从Trace和Debug来看条件编译(Conditional Compilation)

条件编译,顾名思义,就是根据在编译时指定的条件决定最后需要编译的代码。条件编译是我们可以针对某些特性的环境编写相应的代码,比如有写的代码只需要在Debug模式下...

234100

扫码关注云+社区

领取腾讯云代金券