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

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

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

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

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

image

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"调用成功...

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

posted @ 2011-07-11 22:16  Artech  阅读(9580)  评论(16编辑  收藏  举报