WCF授权-WCF自定义授权体系详解[实例篇]
在《WCF授权-WCF自定义授权体系详解[原理篇]》 中,我们谈到WCF自定义授权体系具有两个核心的组件:AuthorizationPolicy和 ServiceAuthorizationManager,已经它们是如何写作最终提供一种基于声明的授权实现。为了让自定义授权有深刻的理解,我们来进 行一个简单实例来演示如何通过自定义这两个组件实现“非角色授权策略”。[源代码从这里下载]
一、创建演示程序解决方案
我们这个实例依然采用简单的计算服务的例子,并且采用如下图所示的解决方案结构。不过,为了后续的授权策略需要,我们在服务契约ICalculator接 口上定义如下四个分别表示加、减、乘、除的四个运算操作。当然服务类型CalculatorService也进行相应的修正。
ICalculator:
1: using System.ServiceModel;
2: namespace Artech.WcfServices.Contracts
3: {
4: [ServiceContract(Namespace = "http://www.artech.com/")]
5: public interface ICalculator
6: {
7: [OperationContract(Action = "http://www.artech.com/calculator/add")]
8: double Add(double x, double y);
9: [OperationContract(Action = "http://www.artech.com/calculator/subtract")]
10: double Subtract(double x, double y);
11: [OperationContract(Action = "http://www.artech.com/calculator/multiply")]
12: double Multiply(double x, double y);
13: [OperationContract(Action = "http://www.artech.com/calculator/divide")]
14: double Divide(double x, double y);
15: }
16: }
CalculatorService:
1: using Artech.WcfServices.Contracts;
2: namespace Artech.WcfServices.Services
3: {
4: public class CalculatorService : ICalculator
5: {
6: public double Add(double x, double y)
7: {
8: return x + y;
9: }
10: public double Subtract(double x, double y)
11: {
12: return x - y;
13: }
14: public double Multiply(double x, double y)
15: {
16: return x * y;
17: }
18: public double Divide(double x, double y)
19: {
20: return x / y;
21: }
22: }
23: }
现在我们的授权策略是这样的:操作Add和Subtract针对仅对用户Foo开放,而Multiply和Divide操作仅对用户Bar开放。 虽然这个简单的授权完全可以通过在相应的服务操作方法上应用PrincipalPermissionAttribute并指定Name属性来实现。但是我 们要尝试通过自定义AuthorizationPolicy和ServiceAuthorizationManager来实现这样的授权策略。先来看看自 定义的AuthorizationPolicy的定义。
二、自定义AuthorizationPolicy
我们将自定义的AuthorizationPolicy创建在Hosting项目中。由于IAuthorizationPolicy定义在 System.IdentityModel程序集中,我们先为Hosting项目添加该程序集的引用。由于授权策略比较简单,我们直接上自定义的 AuthorizationPolicy命名为SimpleAuthorizationPolicy,下面是整个 SimpleAuthorizationPolicy的定义。
1: using System; 2: using System.Collections.Generic; 3: using System.IdentityModel.Claims; 4: using System.IdentityModel.Policy; 5: using System.Linq; 6: namespace Artech.WcfServices.Hosting 7: { 8: public class SimpleAuthorizationPolicy : IAuthorizationPolicy 9: { 10: private const string ActionOfAdd = "http://www.artech.com/calculator/add"; 11: private const string ActionOfSubtract = "http://www.artech.com/calculator/subtract"; 12: private const string ActionOfMultiply = "http://www.artech.com/calculator/multiply"; 13: private const string ActionOfDivide = "http://www.artech.com/calculator/divide"; 14: 15: internal const string ClaimType4AllowedOperation = "http://www.artech.com/allowed"; 16: 17: public SimpleAuthorizationPolicy() 18: { 19: this.Id = Guid.NewGuid().ToString(); 20: } 21: public bool Evaluate(EvaluationContext evaluationContext, ref object state) 22: { 23: if (null == state) 24: { 25: state = false; 26: } 27: bool hasAddedClaims = (bool)state; 28: if (hasAddedClaims) 29: { 30: return true; ; 31: } 32: IList<Claim> claims = new List<Claim>(); 33: foreach (ClaimSet claimSet in evaluationContext.ClaimSets) 34: { 35: foreach (Claim claim in claimSet.FindClaims(ClaimTypes.Name, Rights.PossessProperty)) 36: { 37: string userName = (string)claim.Resource; 38: if (userName.Contains('\\')) 39: { 40: userName = userName.Split('\\')[1]; 41: if (string.Compare("Foo", userName, true) == 0) 42: { 43: claims.Add(new Claim(ClaimType4AllowedOperation,ActionOfAdd, Rights.PossessProperty)); 44: claims.Add(new Claim(ClaimType4AllowedOperation, ActionOfSubtract, Rights.PossessProperty)); 45: } 46: if (string.Compare("Bar", userName, true) == 0) 47: { 48: claims.Add(new Claim(ClaimType4AllowedOperation,ActionOfMultiply, Rights.PossessProperty)); 49: claims.Add(new Claim(ClaimType4AllowedOperation, ActionOfDivide, Rights.PossessProperty)); 50: } 51: } 52: } 53: } 54: evaluationContext.AddClaimSet(this, new DefaultClaimSet(this.Issuer, claims)); 55: state = true; 56: return true; 57: } 58: public ClaimSet Issuer 59: { 60: get { return ClaimSet.System; } 61: } 62: public string Id { get; private set; } 63: } 64: }
我们主要来介绍Evaluate方法中,该方法主要的逻辑是这样的:通过EvaluationContext现有的声明集获取当前的用户名(声明类型和声明权限分别为ClaimTypes.Name和Rights.PossessProperty)。针对获取出来的用户名,创建于被授权服务操作关联的声明。其中声明的三要素(类型、权限和资源)分别为:“http://www.artech.com/allowed”、Rights.PossessProperty和操作的Action。最后将这些声明组成一个声明集添加到EvaluationContext中。
三、自定义ServiceAuthorizationManager
当授权相关的声明集通过自定义的AuthorizationPolicy被初始化之后,我们通过自定义 ServiceAuthorizationManager来分析这些声明,并作做出当前操作是否被授权调用的最终判断。类似于 SimpleAuthorizationPolicy,我们将自定义的ServiceAuthorizationManager起名为 SimpleServiceAuthorizationManager,同样定义于Hosting项目中,下面是整个 SimpleServiceAuthorizationManager类型的定义。
1: using System.IdentityModel.Claims; 2: using System.Security.Principal; 3: using System.ServiceModel; 4: namespace Artech.WcfServices.Hosting 5: { 6: public class SimpleServiceAuthorizationManager : ServiceAuthorizationManager 7: { 8: protected override bool CheckAccessCore(OperationContext operationContext) 9: { 10: string action = operationContext.RequestContext.RequestMessage.Headers.Action; 11: foreach (ClaimSet claimSet in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets) 12: { 13: if (claimSet.Issuer == ClaimSet.System) 14: { 15: foreach (Claim c in claimSet.FindClaims(SimpleAuthorizationPolicy.ClaimType4AllowedOperation, Rights.PossessProperty)) 16: { 17: if (action == c.Resource.ToString()) 18: { 19: GenericIdentity identity = new GenericIdentity(""); 20: operationContext.ServiceSecurityContext.AuthorizationContext.Properties["Principal"] = 21: new GenericPrincipal(identity, null); 22: return true; 23: } 24: } 25: } 26: } 27: return false; 28: } 29: } 30: }
由于基于被授权操作的声明已经通过SimpleAuthorizationPolicy被成功添加到EvaluationContext的声明集列表,并最终作为当前AuthorizationContext声明集的一部分。那么,如果在这些代表被授权操作的声明中,具有一个是基于当前被调用的服务操作的声明,就意味着当前的服务操作调用被授权了的。 这样的逻辑实现在重写的CheckAccessCore方法中。此外,还有一点需要注意的是:在做出成功授权的情况下,需要设置当前的安全主体,因为不管 这个安全主体是否需要,WCF总是会试图从当前AuthorizationContext的属性列表中去获取该安全主体。如果没有,会抛出异常。
四、应用自定义AuthorizationPolicy和ServiceAuthorizationManager
到目前为止,两个核心的自定义对象(SimpleAuthorizationPolicy和 SimpleServiceAuthorizationManager)都已经创建好了,我们现在通过配置的方式将它们设置到应用到服务的 ServiceAuthorizationBehavior服务行为上。下面两段XML片断分别表示服务寄宿和客户端的配置。
服务寄宿配置:
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" serviceAuthorizationManagerType="Artech.WcfServices.Hosting.SimpleServiceAuthorizationManager,Artech.WcfServices.Hosting"> 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>
客户端配置:
1: <?xml version="1.0"?> 2: <configuration> 3: <system.serviceModel> 4: <client> 5: <endpoint name="calculatorService" address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding" contract="Artech.WcfServices.Contracts.ICalculator"/> 6: </client> 7: </system.serviceModel> 8: </configuration>
我们最终需要验证的WCF是否能够按照我们自定义的策略进行授权。为了演示方便,我创建了如下一个名称为Invoke的辅助方法。Invoke方法 的三个参数分别代表进行服务调用的委托、服务代理对象和操作名称。服务操作调用会在该方法中执行,并最终输出相应的文字表示服务调用是否成功。
1: static void Invoke(Action<ICalculator> action, ICalculator proxy, string operation)
2: {
3: try
4: {
5: action(proxy);
6: Console.WriteLine("服务操作\"{0}\"调用成功...", operation);
7: }
8: catch (Exception ex)
9: {
10: Console.WriteLine("服务操作\"{0}\"调用失败...", operation);
11: }
12: }
在如下的代码中,我们分别以用户名Foo和Bar的名义通过上面的Invoke辅助方法对计算服务的四个操作进行访问。而程序执行的最终结果是和我们自定义的授权策略是一致的:用户Foo仅仅授予了调用Add和Substract操作的权限,而其余两个授权给用户Bar。
1: static void Main(string[] args) 2: { 3: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService"); 4: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential; 5: credential.UserName = "Foo"; 6: credential.Password = "Password"; 7: ICalculator calculator = channelFactory.CreateChannel(); 8: Invoke(proxy => proxy.Add(1, 2), calculator, "Add"); 9: Invoke(proxy => proxy.Subtract(1, 2), calculator, "Subtract"); 10: Invoke(proxy => proxy.Multiply(1, 2), calculator, "Multiply"); 11: Invoke(proxy => proxy.Divide(1, 2), calculator, "Divide"); 12: Console.WriteLine(); 13: 14: channelFactory = new ChannelFactory<ICalculator>("calculatorService"); 15: credential = channelFactory.Credentials.Windows.ClientCredential; 16: credential.UserName = "Bar"; 17: credential.Password = "Password"; 18: calculator = channelFactory.CreateChannel(); 19: Invoke(proxy => proxy.Add(1, 2), calculator, "Add"); 20: Invoke(proxy => proxy.Subtract(1, 2), calculator, "Subtract"); 21: Invoke(proxy => proxy.Multiply(1, 2), calculator, "Multiply"); 22: Invoke(proxy => proxy.Divide(1, 2), calculator, "Divide"); 23: 24: Console.Read(); 25: }
输出结果:
1: 服务操作"Add"调用成功...
2: 服务操作"Subtract"调用成功...
3: 服务操作"Multiply"调用失败...
4: 服务操作"Divide"调用失败...
5:
6: 服务操作"Add"调用失败...
7: 服务操作"Subtract"调用失败...
8: 服务操作"Multiply"调用成功...
9: 服务操作"Divide"调用成功...
作者:Artech
出处:http://artech.cnblogs.com/
加支付宝好友偷能量挖...