博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[WCF权限控制]WCF自定义授权体系详解[实例篇]
阅读量:6218 次
发布时间:2019-06-21

本文共 10827 字,大约阅读时间需要 36 分钟。

在《》中,我们谈到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
claims = new List
();
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: 
2: 
3:   
4:     
5:       
 

6: <endpoint address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"

contract="Artech.WcfServices.Contracts.ICalculator"/>

7:       
8:     
9:     
10:       
11:         
 

12: <serviceAuthorization principalPermissionMode="Custom"

serviceAuthorizationManagerType="Artech.WcfServices.Hosting.SimpleServiceAuthorizationManager,Artech.WcfServices.Hosting">

13:             
14:               
15:             
16:           
17:           
18:         
19:       
20:     
21:   
22: 

客户端配置:

1: 
2: 
3:   
4:     
 

5: <endpoint name="calculatorService" address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"

contract="Artech.WcfServices.Contracts.ICalculator"/>

6:     
7:   
8: 

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

1: static void Invoke(Action
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
channelFactory = new ChannelFactory
("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
("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"调用成功...

作者:蒋金楠
微信公众账号:大内老A
微博:
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号
蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
你可能感兴趣的文章
深度学习第19讲:CNN经典论文研读之残差网络ResNet及其keras实现
查看>>
docker 容器内redis连接refuse的问题
查看>>
web.py源码分析: application(1)
查看>>
前言【最爱的贝叶斯哲学】
查看>>
NoClassDefFoundError: ch/qos/logback/classic/spi/ThrowableProxy
查看>>
从 Everything 到 Listary,自 Launch 归 Wox
查看>>
[Guava源码日报](2)Strings分析
查看>>
TKoa 1.0.1 发布,TypeScript 版的 Node.js Koa 框架
查看>>
使用Free Spire.Presentation生成PowerPoint文件
查看>>
PureScript 0.12.3 发布,静态类型语言
查看>>
Derek解读Bytom源码-Api Server接口服务
查看>>
如何优雅地实现Redis命令setbits与getbits
查看>>
Round Robin 轮询调度算法
查看>>
网络测速插件speedtest
查看>>
使用ABAP编程实现对微软Office Word文档的操作
查看>>
Kafka 分布式消息系统
查看>>
spring源码-开篇
查看>>
Python中的魔法方法
查看>>
MIT识物机器人:“秒懂”物体,过目不忘,不用标记数据!
查看>>
【Web API系列教程】3.10 — 实战:处理数据(发布App到Azure App Service)
查看>>