[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: 服务调用失败...

作者:蒋金楠
微信公众账号:大内老A
微博: www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号 蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
相关文章
|
9月前
|
前端开发
WCF更新服务引用报错的原因之一
WCF更新服务引用报错的原因之一
|
6月前
|
Oracle 关系型数据库 API
C# LIS检验系统源码,接口技术:RESTful API + Http+WCF
LIS检验系统一种专门用于医院化验室的计算机系统,它致力于提高医院化验室的工作效率和检测准确率。LIS系统由多个子系统组成,包括样本管理系统、质控系统、检验结果管理系统、报告管理系统等。体系结构:Client/Server架构 SaaS模式 客户端:WPF+Windows Forms 服务端:C# +.Net 数据库:Oracle 接口技术:RESTful API + Http+WCF
|
8月前
|
C# 数据安全/隐私保护
c#如何创建WCF服务到发布(SqlServer版已经验证)
c#如何创建WCF服务到发布(SqlServer版已经验证)
38 0
|
8月前
|
安全 数据库连接 数据库
WCF服务创建到发布(SqlServer版)
在本示例开始之前,让我们先来了解一下什么是wcf? wcf有哪些特点? wcf是一个面向服务编程的综合分层架构。该架构的项层为服务模型层。 使用户用最少的时间和精力建立自己的软件产品和外界通信的模型。它使得开发者能够建立一个跨平台的安全、可信赖、事务性的解决方案。且能与已有系统兼容写作。 简单概括就是:一组数据通信的应用程序开发接口。
57 0
Visual Studio 2022 创建 WCF服务 找不到
Visual Studio 2022 创建 WCF服务 找不到
|
C++
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
102 0
WCF基础教程(二)——解析iis8和iis8.5+VS2013发布wcf服务问题
WCF使用纯代码的方式进行服务寄宿
服务寄宿的目的是为了开启一个进程,为WCF服务提供一个运行的环境。通过为服务添加一个或者多个终结点,使之暴露给潜在的服务消费,服务消费者通过匹配的终结点对该服务进行调用,除去上面的两种寄宿方式,还可以以纯代码的方式实现服务的寄宿工作。
849 0
|
Windows
WCF服务寄宿到IIS
一.WCF简介: Windows Communication Foundation(WCF)是由微软开发的一系列支持数据通信的应用程序框架,可以翻译为Windows 通讯开发平台。整合了原有的windows通讯的 .net Remoting,WebService,Socket的机制,并融合有HTTP和FTP的相关技术。
1046 0
WCF服务自我寄宿
WCF服务的寄宿方式 WCF寄宿方式是一种非常灵活的操作,可以寄宿在各种进程之中,常见的寄宿有: IIS服务、Windows服务、Winform程序、控制台程序中进行寄宿,从而实现WCF服务的运行,为调用者方便、高效提供服务调用。
994 0
|
网络架构
(纯代码)快速创建wcf rest 服务
因为有一个小工具需要和其它的业务对接数据,所以就试一下看能不能弄一个无需配置快速对接的方法出来,百(以)度(讹)过(传)后(讹),最后还是对照wcf配置对象调试出来了: 1.创建WebHttpBinding 2.
978 0