[WCF權限控制]利用WCF自定義授權模式提供當前Principal[實例篇]
在《原理篇》中我們談到:如果采用自定義安全主體權限模式,我們可以通過自定義AuthorizationPolicy或者ServiceAuthorizationManager實現對基于當前認證用于相關的安全主體的提供,進而達到授權的目的。為了讓大家對此有個更加深刻的認識,在這篇文章中我們會提供一個具體的例子。[源代碼從這里下載]
目錄:
一、創建自定義AuthorizationPolicy
二、創建自定義ServiceAuthorizationManager
三、通過自定義AuthorizationPolicy實現授權
四、通過自定義ServiceAuthorizationManager實現授權
一、創建自定義AuthorizationPolicy
我們先來演示通過自定義AuthorizationPolicy以提供當前安全主體的方式。我們通過自定義AuthorizationPolicy實現這樣的授權策略:如果用戶名為Foo(假設為管理員),我們創建一個包含“Administrators”角色的安全主體;而對于其他的用戶,提供的安全主體的角色列表中僅僅包括“Guest”。我們為該自定義AuthorizationPolicy起名為SimpleAdministrators,SimpleAdministrators整個定義如下。
1: public class SimpleAuthorizationPolicy : IAuthorizationPolicy
2: {
3: public SimpleAuthorizationPolicy()
4: {
5: this.Id = Guid.NewGuid().ToString();
6: }
7: public bool Evaluate(EvaluationContext evaluationContext, ref object state)
8: {
9: string userName = string.Empty;
10: foreach (ClaimSet claimSet in evaluationContext.ClaimSets)
11: {
12: foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Name, Rights.PossessProperty))
13: {
14: userName = (string)claim.Resource;
15: }
16: }
17:
18: if (userName.Contains('\\'))
19: {
20: userName = userName.Split('\\')[1];
21: }
22: evaluationContext.Properties["Principal"] = GetPrincipal(userName);
23: return false;
24: }
25:
26: private IPrincipal GetPrincipal(string userName)
27: {
28: GenericIdentity identity = new GenericIdentity(userName);
29: if (string.Compare("Foo", userName, true) == 0)
30: {
31: return new GenericPrincipal(identity, new string[] { "Administrators" });
32: }
33: return new GenericPrincipal(identity, new string[] {"Guest" });
34: }
35:
36: public ClaimSet Issuer
37: {
38: get { return ClaimSet.System; }
39: }
40: public string Id { get; private set; }
41: }
這個安全主體的提供實現在Evaluate方法中,而其中唯一值得一提的是當前認證用戶名的獲取。在客戶端被成功認證之后,被認證的用戶實際上也通過某個聲明(Claim)保存下來。該聲明的類型為“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name”,可以通過ClaimTypes的靜態屬性Name得到。而該Claim對象的Resource就是用戶名。在得到當前認證用戶名之后,相應的GenericPrincipal對象被創建出來,并被置于EvaluationContext的屬性列表中。并且該屬性對應的Key為“Principal”。
二、創建自定義ServiceAuthorizationManager
接下來我們來通過自定義ServiceAuthorizationManager來實現與上面完全一樣的功能,而已授權策略很簡單,我們照例將該自定義ServiceAuthorizationManager起名為SimpleServiceAuthorizationManager。以下是SimpleServiceAuthorizationManager的定義。
1: public class SimpleServiceAuthorizationManager : ServiceAuthorizationManager
2: {
3: protected override bool CheckAccessCore(OperationContext operationContext)
4: {
5: string userName = operationContext.ServiceSecurityContext.PrimaryIdentity.Name;
6: if (userName.Contains('\\'))
7: {
8: userName = userName.Split('\\')[1];
9: }
10: operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = GetPrincipal(userName);
11: return true;
12: }
13: private IPrincipal GetPrincipal(string userName)
14: {
15: GenericIdentity identity = new GenericIdentity(userName);
16: if (string.Compare("Foo", userName, true) == 0)
17: {
18: return new GenericPrincipal(identity, new string[] { "Administrators"});
19: }
20: return new GenericPrincipal(identity, new string[] { "Guest" });
21: }
22: }
和自定義AuthorizationPolicy不同的是,認證用戶的獲取在這里變得更加容易,我們直接可以通過當前ServiceSecurityContext的PrimaryIdentity獲取。需要提醒一下的是,如果你在自定義AuthorizationPolicy的Evaluate方法中調用該屬性,會出現一個StackOverflowException異常,因為該屬性的調用本身又會觸發Evaluate方法的調用。最后被創建的GnericPrincipal被保存在當前AuthorizationContext的屬性列表中,屬性的Key依然是“Principal”。
三、通過自定義AuthorizationPolicy實現授權
現在我們常見一個實例程序來應用我們創建的自定義AuthorizationPolicy,看看它是否能夠起到我們期望的授權的作用。我們依然沿用我們再熟悉不過的計算服務的例子,解決方案依然按照如下圖所示的結構來設計。整個解決方式包括四個項目:Contracts、Services、Hosting和Client。對于這樣的結構我們已經了解得夠多了,在這里沒有必要再贅言敘述了。

在實例解決方案的整個結構建立之后,我們分別在Contracts和Services項目中定義服務契約接口和服務類型。下面是契約接口ICalculator和服務CalculatorService的定義。而在CalculatorService類的Add方法中應用了PrincipalPermissionAttribute特性,并將Roles屬性設置成了Adminstrators,意味著該服務操作只能被管理員用戶組中的用戶調用。
1: [ServiceContract(Namespace = "http://www.artech.com/")]
2: public interface ICalculator
3: {
4: [OperationContract]
5: double Add(double x, double y);
6: }
7:
8: public class CalculatorService : ICalculator
9: {
10: [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
11: public double Add(double x, double y)
12: {
13: return x + y;
14: }
15: }
現在通過Hosting這個控制臺程序對上面創建的服務進行寄宿。下面給出的是整個寄宿程序的配置,從中我們可以看出:應用到CalculatorService的服務行為列表中包含了PrincipalPermissionMode為Custom的ServiceAuthorizationBehavior。而我們定義的SimpleAuthorizationPolicy類型被配置到了<authorizationPolicies>列表中。
1: <?xml version="1.0"?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="useCustomAuthorization">
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="useCustomAuthorization">
12: <serviceAuthorization principalPermissionMode="Custom" >
13: <authorizationPolicies >
14: <add policyType="Artech.WcfServices.Hosting.SimpleAuthorizationPolicy, Artech.WcfServices.Hosting" />
15: </authorizationPolicies>
16: </serviceAuthorization>
17: <serviceDebug includeExceptionDetailInFaults="true"/>
18: </behavior>
19: </serviceBehaviors>
20: </behaviors>
21: </system.serviceModel>
22: </configuration>
由于我們使用了WSHttpBinding,而它在默認的情況下采用Windows客戶端憑證,為此我們需要創建兩個Windows帳號Foo和Bar,密碼被設定為Password。在如下所示的客戶端代碼中,我們分別以Foo和Bar的名義調用了服務。最后將服務能夠成功調用的結果打印出來。
1: class Program
2: {
3: static void Main(string[] args)
4: {
5: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
6: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
7: credential.UserName = "Foo";
8: credential.Password = "Password";
9: ICalculator calculator = channelFactory.CreateChannel();
10: Invoke(calculator);
11:
12: channelFactory = new ChannelFactory<ICalculator>("calculatorService");
13: credential = channelFactory.Credentials.Windows.ClientCredential;
14: credential.UserName = "Bar";
15: credential.Password = "Password";
16: calculator = channelFactory.CreateChannel();
17: Invoke(calculator);
18:
19: Console.Read();
20: }
21: static void Invoke(ICalculator calculator)
22: {
23: try
24: {
25: calculator.Add(1, 2);
26: Console.WriteLine("服務調用成功...");
27: }
28: catch (Exception ex)
29: {
30: Console.WriteLine("服務調用失敗...");
31: }
32: }
33: }
從下面的結果來看,只有在用戶名為Foo才能成功調用服務,而Bar由于權限不足會導致服務調用失敗。這充分證明了通過自定義AuthorizationPolicy能夠正確地起到授權的作用。
1: 服務調用成功...
2: 服務調用失敗...
四、通過自定義ServiceAuthorizationManager實現授權
在證明我們自定義的AuthorizationPolicy確實能夠按照我們定義的策略進行授權之后,我們來試試我們自定義的ServiceAuthorizationManager能否同樣完成授權的使命。為此我們唯一需要做的就是改變一下服務寄宿程序的配置。
1: <?xml version="1.0"?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="useCustomAuthorization">
6: <endpoint address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding"
7: contract="Artech.WcfServices.Contracts.ICalculator"/>
8: </service>
9: </services>
10: <behaviors>
11: <serviceBehaviors>
12: <behavior name="useCustomAuthorization">
13: <serviceAuthorization principalPermissionMode="Custom"
14: serviceAuthorizationManagerType="Artech.WcfServices.Hosting.SimpleServiceAuthorizationManager,
15: Artech.WcfServices.Hosting" >
16: <!--<authorizationPolicies >
17: <add policyType="Artech.WcfServices.Hosting.SimpleAuthorizationPolicy, Artech.WcfServices.Hosting" />
18: </authorizationPolicies>-->
19: </serviceAuthorization>
20: <serviceDebug includeExceptionDetailInFaults="true"/>
21: </behavior>
22: </serviceBehaviors>
23: </behaviors>
24: </system.serviceModel>
25: </configuration>
上面所示的采用自定義ServiceAuthorizationManager實現授權的配置。我們將之前添加的AuthorizationPolicy注釋掉,然后通過ServiceAuthorizationBehavior配置節的serviceAuthorizationManagerType屬性設置成我們自定義的SimpleServiceAuthorizationManager的類型。運行程序后,你會得到和上面一樣的輸出結果。
1: 服務調用成功...
2: 服務調用失敗...
[WCF權限控制]利用WCF自定義授權模式提供當前安全主體[原理篇]
[WCF權限控制]利用WCF自定義授權模式提供當前安全主體[實例篇]


在《原理篇》中我們談到:如果采用自定義安全主體權限模式,我們可以通過自定義AuthorizationPolicy或者ServiceAuthorizationManager實現對基于當前認證用于相關的安全主體的提供,進而達到授權的目的。為了讓大家對此有個更加深刻的認識,在這篇文章中我們會提供一個具體的例子。
浙公網安備 33010602011771號