[WCF權限控製]通過擴展自行實現服務授權[提供源碼下載]
其實針對安全主體的授權實現的原理很簡單,原則上講,隻要你能在服務操作執行之前能夠根據本認證的用戶正確設置當前的安全主體就可以了。如果你了解WCF的整個運行時框架結構,你會馬上想到用於授權的安全主體初始化可以通過自定義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,起名為AuthorizationCallContextInitializerBase。下麵的代碼片斷給出了AuthorizationCallContextInitializerBase的整個定義。AuthorizationCallContextInitializerBase具有一個抽象的方法用於根據當前的安全上下文信息創建安全主體。。為了讓服務操作執行之後當前線程的上下文恢複到執行前的狀態,在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定義如下,它繼承了AuthorizationCallContextInitializerBase,在實現的抽象方法GetPrincipal中創建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具有一個RoleProvider屬性,表示用於獲取當前用戶角色列表的RoleProvider,該屬性在構造函數中被初始化。在實現的GetPrincipal抽象方法中,。
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="https://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
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-26 16:33:58