[WCF权限控制]WCF自定义授权体系详解[实例篇]
在《原理篇》中,我们谈到WCF自定义授权体系具有两个核心的组件:AuthorizationPolicy和ServiceAuthorizationManager,已经它们是如何写作最终提供一种基于声明的授权实现。为了让自定义授权有深刻的理解,我们来进行一个简单实例来演示如何通过自定义这两个组件实现“非角色授权策略”。[源代码从这里下载]
目录:
一、创建演示程序解决方案
二、自定义AuthorizationPolicy
三、自定义ServiceAuthorizationManager
四、应用自定义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"调用成功...