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