Windows用户组安全主体权限模式,顾名思义,就是将利用Windows安全系统将对应的Windows帐号所在的用户组作为该用户权限集的授权方式。认证和授权密不可分,但是对于认证和授权在WCF安全体系中的实现来说,它们则是相对独立的。认证属于安全传输的范畴,是在信道层实现的,而授权则是在服务模型层实现的。但是对于基于Windows用户组的授权来说,最终体现出来的授权行为却和采用何种认证具有密切的关系。
一、Windows用户组授权与认证的关系
无论是对于基于Windows用户组还是基于ASP.NET Roles提供程序的授权,最终都体现在创建相应的安全主体,并将其附加到当前线程上。对于Windows用户组模式来说,有一点是肯定的:不论采用何种客户端凭证类型以及认证模式,最终建立的安全主体都是一个WindowsPrincipal,并且这个WindowsPrincipal对应的安全身份是一个WindowsIdentity。但是该WindowsPrincipal的Identity能否正确地反映被认证后的用户,以及其本身能够正确反映该认证用户的权限,就和认证有密切的关系。
具体来说,当你选择了Windows用户组安全主体权限模式,只有在采用Windows认证的情况下最终生成的安全主体才能正确地反映被认证的用户。这里的Windows认证包括如下三种情况:
- 客户端凭证为Windows凭证;
- 客户端凭证为用户名/密码凭证,并采用Windows认证模式;
- 客户端凭证为X.509证书凭证,并允许与Windows帐号进行映射。
在其他情况下,最终被创建的是一个“空”的WindowsPrincipal。这个空的WindowsPrincipal不仅仅体现在具有一个“空”的权限集,而且其内部的WindowsIdentity也为“空”。该WindowsIdentity具有如下面列表所示的属性。
- Name:空字符串
- AuthenticationType: 空字符串
- IsAuthenticated:False
- Groups:Null
- IsAnonymous:True
- IsGuest:False
- IsSystem:False
在非Windows认证的情况下,即使存在着一个与认证用户一致的Windows帐号,WCF授权系统都不会基于该Windows帐号来创建最终的WindowsPrincipal。举个例子,假设服务寄宿端所在的域中具有一个用户叫做“张三”,并且存在于当前机器的管理员(Administrators)用户组中。现在我们对某个服务操作进行授权,要求必须在具有管理员权限才能被调用。在进行服务寄宿的时候,终结点的绑定采用用户名/密码作为客户端凭证,并选择Membership认证模式。在认证成功的情况下,被授权的服务操作也是不能被正常调用的。
通过前面一篇文章的介绍,我们知道了WCF采用怎样的授权的方式通过ServiceAuthorizationBehavior这一服务行为来控制。所以针对授权的编程主要就体现在对该服务行为的设置。对于Windows用户组授权来说,我们只需要将ServiceAuthorizationBehavior的PrincipalPermissionMode属性设置成PrincipalPermissionMode.UseWindowsGroups即可。
二、ServiceAuthorizationBehavior服务行为的设置
既然ServiceAuthorizationBehavior是一个服务行为,我们只需要通过编程或者配置的方式将该服务行为添加到当前服务的行为列表中就可以了。你可以按照下面的编程方式让寄宿的服务采用基于Windows用户组授权模式。
1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: ServiceAuthorizationBehavior behavior = host.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
4: if (null == behavior)
5: {
6: behavior = new ServiceAuthorizationBehavior();
7: host.Description.Behaviors.Add(behavior);
8: }
9: behavior.PrincipalPermissionMode = PrincipalPermissionMode.UseWindowsGroups;
10: host.Open();
11: //...
12: }
此外在ServiceHost你也可以通过ServiceHost的只读属性Authorization得到这个ServiceAuthorizationBehavior对象。如下面的代码片断所示,该属性实际上是定义在ServiceHost的基类ServiceHostBase中。
1: public abstract class ServiceHostBase
2: {
3: //其他成员
4: public ServiceAuthorizationBehavior Authorization { get; }
5: }
在读取该属性的时候,如果当前服务描述中的服务行为列表中找不到ServiceAuthorizationBehavior,系统会自动创建一个ServiceAuthorizationBehavior对象并添加到服务行为列表中。所以对于上面的这段服务寄宿代码实际和下面是完全等效的。
1: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
2: {
3: host.Authorization.PrincipalPermissionMode = PrincipalPermissionMode.UseWindowsGroups;
4: host.Open();
5: //...
6: }
我们依然推荐采用配置的方式进行授权模式的设置。而下面一段配置和上面的代码在作用上是等效的。
1: <configuration>
2: <system.serviceModel>
3: <services>
4: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="UseWindowsGroupsAuthorization">
5: <endpoint address="http://127.0.0.1/calculatorservice" binding="ws2007HttpBinding" contract="Artech.WcfServices.Contracts.ICalculator"/>
6: </service>
7: </services>
8: <behaviors>
9: <serviceBehaviors>
10: <behavior name="UseWindowsGroupsAuthorization">
11: <serviceAuthorization principalPermissionMode="UseWindowsGroups"/>
12: </behavior>
13: </serviceBehaviors>
14: </behaviors>
15: </system.serviceModel>
16: </configuration>
为了让读者对基于Windows用户组的授权具有深刻的认识,接下来我们通过一个简单的事例来讲解在真正的应用中该授权模式如何使用。对于接下来演示的事例,我们将采用Windows认证和授权。至于授权的最终实现,我们采用的是在服务方法上面应用PrincipalPermissionAttribute特性方式的声明式授权。[源代码从这里下载]
目录:
步骤一、创建测试帐号
步骤二、创建服务契约和服务
步骤三、寄宿服务
步骤四、创建客户端程序
步骤一、创建测试帐号
在创建事例解决方案之前我们先完成相应的准备工作,创建两个测试用的Windows帐号。假设两个帐号的名称分别为Foo和Bar,密码为Password。然后将帐号Foo添加到管理员(Administrators)用户组中。
步骤二、创建服务契约和服务
我们依然沿用我们再熟悉不过的计算服务的例子,解决方案依然按照如下图所示的结构来设计。整个解决方式包括四个项目:Contracts、Services、Hosting和Client。对于这样的结构我们已经了解得够多了,在这里没有必要再赘言叙述了。
在实例解决方案的整个结构建立之后,我们分别在Contracts和Services项目中定义服务契约接口和服务类型。下面是契约接口ICalculator和服务CalculatorService的定义。而在CalculatorService类的Add方法中应用了PrincipalPermissionAttribute特性,并将Roles属性设置成了Adminstrators,意味着该服务操作只能被管理员用户组中的用户调用。
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]
8: double Add(double x, double y);
9: }
10: }
CalculatorService:
1: using System.Security.Permissions;
2: using Artech.WcfServices.Contracts;
3: namespace Artech.WcfServices.Services
4: {
5: public class CalculatorService : ICalculator
6: {
7: [PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
8: public double Add(double x, double y)
9: {
10: return x + y;
11: }
12: }
13: }
步骤三、寄宿服务
现在通过Hosting这个控制台程序对上面创建的服务进行寄宿。下面给出的是整个寄宿程序的配置。从该配置我们可以看到,服务唯一的终结点采用的绑定类型为WS2007HttpBinding。而在默认的情况下,WS2007HttpBinding采用Message安全模式和Windows认证方式。此外,基于UseWindowsGroups安全主体权限模式的ServiceAuthorization服务行为被应用到了该服务上。
1: <?xml version="1.0"?>
2: <configuration>
3: <system.serviceModel>
4: <services>
5: <service name="Artech.WcfServices.Services.CalculatorService" behaviorConfiguration="useWindowsGroupsAuthorization">
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="useWindowsGroupsAuthorization">
13: <serviceAuthorization principalPermissionMode="UseWindowsGroups"/>
14: </behavior>
15: </serviceBehaviors>
16: </behaviors>
17: </system.serviceModel>
18: </configuration>
而服务寄宿的程序依然简洁如故,仅仅包括正对寄宿服务类型的ServiceHost的创建和开启而已。
1: using System.ServiceModel;
2: using Artech.WcfServices.Services;
3: using System;
4: namespace Artech.WcfServices.Hosting
5: {
6: public class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ServiceHost host = new ServiceHost(typeof(CalculatorService)))
11: {
12: host.Open();
13: Console.Read();
14: }
15: }
16: }
17: }
步骤四、创建客户端程序
来到整个实例的最后一个步骤,我们将服务调用的客户程序定义在Client项目中。整个实例演示的目的在于确认针对服务操作Add的授权根据Windows用户组进行的,我们只需要关注被授权的服务操作是否被成功调用。为此,我写了如下一个简单的辅助性的方法Invoke。如果服务操作被成功执行,输出“服务调用成功”,如果抛出异常则输出“服务调用失败”。
1: static void Invoke(ICalculator calculator)
2: {
3: try
4: {
5: calculator.Add(1,2);
6: Console.WriteLine("服务调用成功...");
7: }
8: catch (Exception ex)
9: {
10: Console.WriteLine("服务调用失败...");
11: }
12: }
下面演示了完整的客户端程序和响应的配置。整个程序体现了两次针对相同服务操作的调用,而两次服务调用采用的客户端凭证分别是基于之前创建的两个Windows帐号Foo和Bar。
客户端程序:
1: using System.Net;
2: using System.ServiceModel;
3: using Artech.WcfServices.Contracts;
4: namespace Artech.WcfServices.Clients
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10:
11: ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculatorService");
12: NetworkCredential credential = channelFactory.Credentials.Windows.ClientCredential;
13: credential.UserName = "Foo";
14: credential.Password = "Password";
15: ICalculator calculator = channelFactory.CreateChannel();
16: Invoke(calculator);
17:
18: channelFactory = new ChannelFactory<ICalculator>("calculatorService");
19: credential = channelFactory.Credentials.Windows.ClientCredential;
20: credential.UserName = "Bar";
21: credential.Password = "Password";
22: calculator = channelFactory.CreateChannel();
23: Invoke(calculator);
24:
25: Console.Read();
26: }
27:
28: }
29: }
配置:
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"
6: contract="Artech.WcfServices.Contracts.ICalculator"/>
7: </client>
8: </system.serviceModel>
9: </configuration>
由于调用的服务操作需要具有管理员权限采用调用,所以以Foo名义进行调用是没有为题的,但是对于帐号Bar,由于权限不足将会调用失败。而客户端输出的结果反映了这一点。
1: 服务调用成功...
2: 服务调用失败...
注: 对于这个事例演示来说,服务操作只有具有管理员权限方能被正常调用。虽然我们创建的Windows帐号Foo在管理员用户组中,但是如果你使用Vista、Windows Server 2008和Windows 7这三种操作系统,在UAC开启的情况下,即使你以管理员运行我们的演示程序,Foo也不具有管理员权限。所以,你需要关闭UAC采用得到正确的执行结果,否则两次调用都是输出“服务调用失败...”。