[WCF权限控制]通过扩展自行实现服务授权[提供源码下载]
其实针对安全主体的授权实现的原理很简单,原则上讲,只要你能在服务操作执行之前能够根据本认证的用户正确设置当前的安全主体就可以了。如果你了解WCF的整个运行时框架结构,你会马上想到用于授权的安全主体初始化可以通过自定义CallContextInitializer来实现。[源代码从这里下载]
目录: CallContextInitializer简介 步骤一、自定义CallContextInitializer 步骤二、创建服务行为 步骤三、使用服务行为进行授权
CallContextInitializer简介
对于WCF的整个运行时框架来说,CallContextInitializer是一个重要的对象。一个运行时服务操作(DispatchOperation)具有一个CallContextInitializer列表。而每一个CallContextInitializer实现ICallContextInitializer接口。如下面的代码片断所示,ICallContextInitializer具有两个方法BeforeInvoke和AfterInvoke。它们分别在操作方法之前前后进行调用上下文的初始化和清理操作。那么我么就可以自定义CallContextInitializer,在BeforeInvoke中初始化当前的安全主体。
1: public interface ICallContextInitializer
2: {
3: void AfterInvoke(object correlationState);
4: object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message);
5: }
步骤一、自定义CallContextInitializer
我们授权自定义一个抽象的CallContextInitializer,起名为AuthorizationCallContextInitializerBase。下面的代码片断给出了AuthorizationCallContextInitializerBase的整个定义。AuthorizationCallContextInitializerBase具有一个抽象的方法GetPrincipal用于根据当前的安全上下文信息创建安全主体。该方法会在BeforeInvoke方法被调用,返回值被设置成当前线程的安全主体。为了让服务操作执行之后当前线程的上下文恢复到执行前的状态,在BeforeInvoke方法中当前的安全主体被保存下来,并传递给AfterInvoke方法中恢复当前线程的原来的安全主体。
1: public abstract class AuthorizationCallContextInitializerBase: ICallContextInitializer
2: {
3: public void AfterInvoke(object correlationState)
4: {
5: IPrincipal principal = correlationState as IPrincipal;
6: if (null != principal)
7: {
8: Thread.CurrentPrincipal = principal;
9: }
10: }
11: public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
12: {
13: var originalPrincipal = Thread.CurrentPrincipal;
14: Thread.CurrentPrincipal = this.GetPrincipal(ServiceSecurityContext.Current);
15: return originalPrincipal;
16: }
17: protected abstract IPrincipal GetPrincipal(ServiceSecurityContext serviceSecurityContext);
18: }
基于两种安全主体权限模式,我们创建了两个具体的CallContextInitializer。第一个为基于Windows用户组的WindowsAuthorizationCallContextInitializer。WindowsAuthorizationCallContextInitializer定义如下,它继承了AuthorizationCallContextInitializerBase,在实现的抽象方法GetPrincipal中根据当前ServiceSecurityContext的WindowsIdentity属性创建WindowsPrincipal。
1: public class WindowsAuthorizationCallContextInitializer:AuthorizationCallContextInitializerBase
2: {
3: protected override IPrincipal GetPrincipal(ServiceSecurityContext serviceSecurityContext)
4: {
5: WindowsIdentity identity = serviceSecurityContext.WindowsIdentity;
6: if (null == identity)
7: {
8: identity =WindowsIdentity.GetAnonymous();
9: }
10: return new WindowsPrincipal(identity);
11: }
12: }
而基于ASP.NET Roles安全主体权限模式的安全主体初始化实现在如下所示的AspRoleAuthorizationCallContextInitializer类中。AspRoleAuthorizationCallContextInitializer具有一个RoleProvider属性,表示用于获取当前用户角色列表的RoleProvider,该属性在构造函数中被初始化。在实现的GetPrincipal抽象方法中,借助于RoleProvider获取基于当前用户的所有角色,并创建GenericPrincipal。
1: public class AspRoleAuthorizationCallContextInitializer : AuthorizationCallContextInitializerBase
2: {
3: public RoleProvider RoleProvider { get; private set; }
4: public AspRoleAuthorizationCallContextInitializer(RoleProvider roleProvider)
5: {
6: this.RoleProvider = roleProvider;
7: }
8: protected override IPrincipal GetPrincipal(ServiceSecurityContext serviceSecurityContext)
9: {
10: var userName = serviceSecurityContext.PrimaryIdentity.Name;
11: var identity = new GenericIdentity(userName);
12: var roles = this.RoleProvider.GetRolesForUser(userName);
13: return new GenericPrincipal(identity, roles);
14: }
15: }
步骤二、创建服务行为
现在,用户进行安全主体初始化的两个具体的CallContextInitializer已经创建完成,现在需要做的工作就是将其应用到WCF的运行时框架体系之中。为此,我们创建了如下一个服务行为ServiceAuthorizationBehaviorAttribute。ServiceAuthorizationBehaviorAttribute是一个自定义特性,并实现了IServiceBehavior接口。它具有两个两个属性:PrincipalPermissionMode和CallContextInitializer。前者在构造函数中指定,我们根据该参数决定具体创建的CallContextInitializer类型,是WindowsAuthorizationCallContextInitializer还是AspRoleAuthorizationCallContextInitializer。而构造函数中具有一个可选的参数roleProviderName表示采用的RoleProvider配置名称。
1: [AttributeUsage( AttributeTargets.Class)]
2: public class ServiceAuthorizationBehaviorAttribute: Attribute, IServiceBehavior
3: {
4: public PrincipalPermissionMode PrincipalPermissionMode { get; private set; }
5: public ICallContextInitializer CallContextInitializer { get; private set; }
6:
7: public ServiceAuthorizationBehaviorAttribute(PrincipalPermissionMode principalPermissionMode, string roleProviderName = "")
8: {
9: switch (principalPermissionMode)
10: {
11: case PrincipalPermissionMode.UseWindowsGroups:
12: {
13: this.CallContextInitializer = new WindowsAuthorizationCallContextInitializer();
14: break;
15: }
16: case PrincipalPermissionMode.UseAspNetRoles:
17: {
18: if (string.IsNullOrEmpty(roleProviderName))
19: {
20: this.CallContextInitializer = new AspRoleAuthorizationCallContextInitializer(Roles.Provider);
21: }
22: else
23: {
24: this.CallContextInitializer = new AspRoleAuthorizationCallContextInitializer(Roles.Providers[roleProviderName]);
25: }
26: break;
27: }
28: case PrincipalPermissionMode.Custom:
29: {
30: throw new ArgumentException("只有UseWindowsGroups和UseAspNetRoles模式被支持!");
31: }
32: }
33: }
34:
35: public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { }
36:
37: public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
38: {
39: if (null == this.CallContextInitializer)
40: {
41: return;
42: }
43:
44: foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers)
45: {
46: foreach (EndpointDispatcher endpoint in channelDispatcher.Endpoints)
47: {
48: foreach (DispatchOperation operation in endpoint.DispatchRuntime.Operations)
49: {
50: operation.CallContextInitializers.Add(this.CallContextInitializer);
51: }
52: }
53: }
54: }
55: public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { }
56: }
CallContextInitializer的注册实现在ApplyDispatchBehavior方法中,逻辑很简单:遍历所有信道分发器(ChannelDispatcher),每个信道分发器的所有终结点分发器(EndpointDispatcher),以及每个终结点分发器对应的分发运行时(DispatchRuntime)的所有运行时操作(DispatchOperation)。最后将初始化的CallContextInitializer添加到操作的CallContextInitializer列表中。
步骤三、使用服务行为进行授权
由于上面定义的服务行为ServiceAuthorizationBehaviorAttribute是一个自定义特性,所以我们可以直接将其应用到服务类型上。我们直接采用《基于Windows用户组的授权方式[下篇]》的例子。如下所示,在服务类型CalculatorService上应用了ServiceAuthorizationBehaviorAttribute特性,并采用了UseWindowsGroups安全主体权限模式。
1: [ServiceAuthorizationBehavior(PrincipalPermissionMode.UseWindowsGroups)]
2: public class CalculatorService : ICalculator
3: {
4: [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
5: public double Add(double x, double y)
6: {
7: return x + y;
8: }
9: }
为了证明我们自定义的服务行为也能和ServiceAuthorizationBehavior一样实现正确的授权,我们需要将ServiceAuthorizationBehavior的授权功能关闭。为此我们修正了服务端的配置,将ServiceAuthorizationBehavior的PrincipalPermissionMode设置为None。
1: <?xml version="1.0"?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="disableAuthorization">
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="disableAuthorization">
12: <serviceAuthorization principalPermissionMode="None"/>
13: </behavior>
14: </serviceBehaviors>
15: </behaviors>
16: </system.serviceModel>
17: </configuration>
而客户端的服务调用程序中,依然是分别以Foo和Bar(Foo具有管理员权限)的名义进行两次服服务调用。由于两个Windows帐号权限的不同,同样只有第一个服务调用能够成功,这反映在最终的执行结果中。客户端程序:
1: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
2: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
3: credential.UserName = "Foo";
4: credential.Password = "Password";
5: ICalculator calculator = channelFactory.CreateChannel();
6: Invoke(calculator);
7:
8: channelFactory = new ChannelFactory<ICalculator>("calculatorService");
9: credential = channelFactory.Credentials.Windows.ClientCredential;
10: credential.UserName = "Bar";
11: credential.Password = "Password";
12: calculator = channelFactory.CreateChannel();
13: Invoke(calculator);
输出结果:
1: 服务调用成功...
2: 服务调用失败...
- PHP7新特性介绍
- VFS四大对象之四-struct file
- 《Redis设计与实现》读书笔记(二) ——Redis中的字典(Hash)
- 《Redis设计与实现》读书笔记(三) ——Redis中的链表
- 《Redis设计与实现》读书笔记(四) ——Redis中的跳跃表
- 解析Linux中的VFS文件系统之文件系统的注册(二)
- vivi虚拟摄像头驱动程序
- 系统架构 | 基于微服务架构,改造企业核心系统之实践
- 《Redis设计与实现》读书笔记(五) ——Redis中的整数集合
- 《Redis设计与实现》读书笔记(七) ——Redis对象综述及字符串对象实现原理
- 自动登录脚本
- 极致之处,精彩无限 - 优化了一半的SQL
- 编程修炼 | Scala中Stream的应用场景及其实现原理
- Linux之内存描述符mm_struct
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Linux atop监控
- go modules中使用replace用法
- 分布式文件系统Ceph的挂载方式
- 在CentOS 7上挂载CephFS
- Qt音视频开发14-mpv读取和控制
- 深入理解Nginx的rewrite模块
- max os 安装各种问题/Error解决方法
- 不要启用 net.ipv4.tcp_tw_recycle
- 利用SSH(无密码)免登录来节省你的生命
- 深入linux下磁盘Disk,分区Partition,挂载Mount
- 净化Git之rebase变基的使用
- Linux内存管理 一个进程究竟占用多少空间?-VSS/RSS/PSS/USS
- Bash:选择结构之case
- ElasticSearch的Reindex
- 10行Python代码自动清理电脑内重复文件,解放双手!