基础
WCF 中的代码访问安全性,第 1 部分
Juval Lowy

目录
在Microsoft® .NET Framework 1.0 中引入的代码访问安全性 (CAS) 可能是 .NET 与非托管代码相比唯一有所区别的功能。CAS 内置于 .NET Framework 的每个结构中,它影响托管代码中的每项操作,这些是非托管代码永远无法做到的。
Windows® Communication Foundation (WCF) 的第一个版本并不支持 CAS;System.ServiceModel 程序集不允许禁用了 CAS 支持的任何部分信任的调用方。第二版在某些 HTTP 绑定中引入了对 CAS 的初步支持,但仅限于有限的几种方案。这一更改使我能够着手编写小型框架,可以在不影响 WCF 编程模型或 CAS 的情况下提供对 CAS 的全面支持。
在有关 CAS 的两个专栏的第一个中,我将简要讨论 WCF 中的代码访问安全性,然后再介绍我的一个解决方案,它可以针对 WCF 服务启用部分信任的客户端。
CAS 概览
.NET Framework 定义了 24 种不同的安全权限,它们几乎可以控制任何类型的操作。这些权限包括文件 I/O、UI、反射、安全性、网络、数据访问等各个方面。权限类型可以应用到特定的资源,例如文件 I/O 权限中的读取特定文件的权限,或者 UI 权限中的显示特定类型窗口的权限。它也可以是完全拒绝(例如拒绝执行任何文件 I/O 操作)或完全授予(例如不受限制的文件 I/O 访问)。
权限被分组为权限集,并为每个程序集都分配了一个。.NET Framework 定义了一些标准权限集,例如 FullTrust(暗含所有权限)或 Execution(仅可访问 CPU 的权限)。管理员可以使用 .NET Configuration 工具定义自定义权限集,而开发人员则可以通过编程方式来定义(或使用权限集文件,或使用应用程序所需的权限集来定义)ClickOnce 应用程序清单。
加载程序集时,CLR 会为各个程序集分配其权限。程序集将提供某种形式的身份证明以获得这些权限。其中包括检查程序集加载位置的基于来源的证明(例如,所有来自全局程序集缓存 (GAC) 的代码都被授予完全信任权限),或者检查程序集本身某些方面的基于内容的证明(例如,程序集的强名称)。
每个应用程序域始终都会被分配一个被称为应用程序域安全策略的权限集,任何加载到该应用程序域的程序集都被限定为该权限集;否则将会出现安全异常。新的应用程序域在开始时都具有 FullTrust 权限集,并且由于源自本地计算机的所有代码默认都将获得 FullTrust 权限集,所以大多数基于 .NET 的应用程序都以预设方式工作,与根本不使用 CAS 的效果完全一样。这使得代码(以及用户、数据、计算机甚至网络)易于遭受各种形式的破坏,从病毒或蠕虫之类的安全攻击到单纯的人为错误。
以低于完全信任级别运行的代码称为部分信任的代码。无论何时,托管代码的任何片断试图使用 .NET Framework 访问资源或执行任何操作时(包括与非托管代码之间的互操作),.NET Framework 都会验证包含该代码的程序集是否具有执行该操作所需的权限。如果该程序集缺少所需的权限,.NET Framework 将抛出安全异常,从而中止该操作。
由于受信任的程序集可能会被恶意的、不太受信任的程序集引诱执行某些不太受信任的程序集没有权限执行的操作,所以仅对执行某项操作的程序集提出权限要求尚嫌不够。因此,.NET Framework 会遍历整个调用方堆栈,确保堆栈中的每个调用方都具有必要的权限。这种堆栈遍历被称为“安全要求”,无论执行程序集的权限如何都必须满足此要求。
您的代码也可以声明权限——即,声明堆栈中的每个调用方都具有所要求的权限。权限声明具有终止堆栈遍历的作用。您的代码只能声明它所具有的权限,并且这还需要额外的特殊安全声明权限。
在声明一项权限的同时要求另一项就位是一种不错的想法。开发人员可以使用专门的权限类或使用匹配的属性集通过编程方式要求或声明权限。开发人员也可以在程序集、类或方法级别主动拒绝权限。拒绝权限或仅允许执行代码所需的有限权限集可以减少受到引诱攻击的几率。有关代码访问安全性的更多信息,请参阅我编写的《Programming .NET Components 2nd Edition》一书中的第 12 章,我在其中使用超过 100 页的篇幅介绍了这种基础方法及其应用。
客户端 CAS
在 .NET Framework 3.5 中,WCF 只允许在有限的几种情况下执行部分信任的代码。WCF 在部分信任的情况下仅允许调用 HTTP 绑定 BasicHttpBinding、WSHttpBinding 和 WebHttpBinding(不包括 WSDualHttpBinding),而且不提供任何安全性或仅提供传输安全性。另外,对于 WSHttpBinding,消息安全性、可靠的消息传递以及事务等各方面行为都被禁止。所有启用部分信任的绑定都必须使用文本编码。客户端不能使用类似诊断的附加 WCF 功能。为了能够在部分信任的环境中使用,System.ServiceModel 程序集必须在程序集定义中包括 AllowPartiallyTrustedCallers 属性以允许部分信任的调用方:
[assembly: AllowPartiallyTrustedCallers]
在 WCF 的第一版中,省略此属性将阻止所有有关部分信任的使用。在 .NET Framework 3.5 中,强制执行受限的支持功能集现在由绑定负责。每个非 HTTP 绑定都会主动要求其调用方的完全信任,无论是客户端代理还是服务主机都要如此。允许的 HTTP 绑定本身不需要完全信任,但需要根据使用的上下文来确定所需的权限。在客户端,这些绑定要求知道执行权限(带有执行标记的安全权限)以及连接到服务的权限(带有到目标 URI 的连接标记的 Web 权限)。
除了这些对客户端的要求以外,还有其他一些对配置的限制。例如,配置文件不能包含任何对证书存储区(用于客户端证书凭据)的引用,因为访问证书存储区会使 WCF 请求完全信任权限。
理想情况下,您会希望能够领略 WCF 的完整功能,从分布式事务到可靠调用、各种安全性凭据类型、通过 TCP 和进程间通信 (IPC) 渠道(例如命名管道)进行的 intranet(甚至是同一台计算机)应用程序通信,完成所有这一切而无需考虑 CAS——即,不必依赖于完全信任权限。
部分信任的客户端
要使任何部分信任级别的客户端都能够使用所有 WCF 功能和绑定,必须阻止绑定对完全信任的需要。实现此目标的唯一方法是使代理自我声明完全信任。可以通过 PermissionSetAttribute 声明完全信任,只需使用 SecurityAction 枚举值的 Assert 标记并为权限名称指定类型安全字符串 "FullTrust" 即可:
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
尽管权限名称是使用字符串指定的,但可以通过编译器来增加安全性,因为它可以检验允许的值。
此外,必须防止客户端直接访问 ClientBase<T> 基类的任何方法(这仍然需要完全信任),因此代理需要隐藏常用的 Close 和 Dispose 方法。也可以通过代理类自身访问 ClientBase<T> 的方法或属性(例如 Channel 或构造函数),因为代理声明了完全信任。
问题在于为了声明完全信任,代理自身必须被授予完全信任,而这是部分信任的客户端无法首先提供的。因此,您需要将代理类从其自身程序集中分离出来并将其标记为公有,然后授予该程序集完全信任。您可以使用 .NET Framework 2.0 Configuration 控制面板小程序完成此操作——只需使用一些基于内容的证明(例如其强名称)标识代理的程序集并授予程序集完全信任即可。
您还可以在客户端的 GAC 中安装代理程序集。因为所有来自 GAC 的程序集都被授予完全信任,所以代理也将获得完全信任。您还需记住要声明 AllowPartiallyTrustedCallers 属性;这将使部分信任的调用方能够调用该程序集。
最后,您需要将代理所使用的约定定义添加到代理的程序集中(并将约定标记为公共)。这是必需的操作,因为 WCF 要求上级调用链的所有程序集都是完全信任的,如果约定来自部分信任的程序集,请求将失败。图 1 显示的是这些约定和代理定义的示例。

Figure 1 通过代理声明完全信任
[assembly: AllowPartiallyTrustedCallers]
[ServiceContract]
public interface IMyContract
{
[OperationContract]
void MyMethod();
}
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public class MyContractClient :
ClientBase<IMyContract>,IMyContract,IDisposable
{
public MyContractClient() {}
public MyContractClient(string endpointName) : base(endpointName) {}
/* More constructors */
public void MyMethod() {
Channel.MyMethod();
}
public new void Close() {
base.Close();
}
void IDisposable.Dispose() {
Close();
}
}
客户端请求
遗憾的是,图 1 中显示的技术具有潜在的安全漏洞。通过抑制 WCF 的安全请求,任何部分信任的客户端现在都可以调用任何 WCF 服务。假设有个客户端没有被授予连接到 TCP 套接字或网站的权限。虽然该客户端无法直接使用网络,但是通过从受限制的客户端环境越过 WCF 进行调用,它可以绕过此限制。
该问题的解决方案是使用专门的 ClientBase<T> 的子类,它一方面可以声明空白 WCF 请求以获得完全信任,另一方面它将根据客户端试图执行的操作来请求相应的特定安全权限。我的 PartialTrustClientBase<T> 类即是这样一种代理类,如图 2 所示。

Figure 2 PartialTrustClientBase
类
public abstract class PartialTrustClientBase<T> :
ClientBase<T>,IDisposable
where T : class
{
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public PartialTrustClientBase() {}
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public PartialTrustClientBase(string endpointName) :
base(endpointName) {}
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public PartialTrustClientBase(Binding binding,
EndpointAddress remoteAddress) :
base(binding,remoteAddress) {}
//Useful only for clients that want full-brunt raw demands from WCF
protected new T Channel
{
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
get {
return base.Channel;
}
}
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
new public void Close() {
base.Close();
}
void IDisposable.Dispose() {
Close();
}
protected object Invoke(string operation,params object[] args) {
if(IsAsyncCall(operation)) {
DemandAsyncPermissions();
}
DemandSyncPermissions(operation);
CodeAccessSecurityHelper.PermissionSetFromStandardSet(
StandardPermissionSet.FullTrust).Assert();
Type contract = typeof(T);
MethodInfo methodInfo = contract.GetMethod(operation);
return methodInfo.Invoke(Channel,args);
}
protected virtual void DemandAsyncPermissions() {
CodeAccessSecurityHelper.DemandAsyncPermissions();
}
protected virtual void DemandSyncPermissions(string operationName) {
this.DemandClientPermissions(operation);
}
bool IsAsyncCall(string operation) {
if(operation.StartsWith("Begin")) {
MethodInfo info = typeof(T).GetMethod(operation);
object[] attributes = info.GetCustomAttributes(
typeof(OperationContractAttribute),false);
Debug.Assert(attributes.Length == 1);
return (attributes[0] as
OperationContractAttribute).AsyncPattern;
}
return false;
}
}
使用 PartialTrustClientBase<T> 就像使用常规代理基类一样。您仍需对从中派生出的代理类授予完全信任权限,并允许部分信任的调用方。但是,与图 1 不同,PartialTrustClientBase<T> 不能在类级别上声明完全信任。相反,当需要时它可以在本地声明完全信任。此外,PartialTrustClientBase<T> 也可以用于请求其他 CAS 权限。
如果您从 PartialTrustClientBase<T> 派生代理类,并如此处所示使代理声明完全信任,则在调用客户端上将不会发出任何请求:
[PermissionSet(SecurityAction.Assert,Name = "FullTrust")]
public class MyContractClient :
PartialTrustClientBase<IMyContract>,IMyContract
{
public MyContractClient() {}
public MyContractClient(string endpointName) :
base(endpointName) {}
public void MyMethod() {
Channel.MyMethod();
}
}
此代码和 图 1 所示代码之间的唯一差别是新版本更简洁,因为现在由 PartialTrustClientBase<T> 方法来实现 Close 和 Dispose 的隐藏。这一新代理仍会抑制 WCF 发出的所有安全请求并使部分信任的客户端暴露在一些引诱攻击面前,或使部分信任的客户端比预期执行更多的操作。
原始 WCF 请求
对 PartialTrustClientBase<T> 更具安全意识的使用方法是不要使代理声明完全信任:
public class MyContractClient :
PartialTrustClientBase<IMyContract>,IMyContract
{
public MyContractClient() {}
public MyContractClient(string endpointName) :
base(endpointName) {}
public void MyMethod() {
Channel.MyMethod();
}
}
要支持这种使用方法,PartialTrustClientBase<T> 必须在其构造函数和 Close 方法中明确声明完全信任。此外,PartialTrustClientBase<T> 将隐藏 ClientBase<T> 的 Channel 属性,并在 get 访问器中声明完全信任。这将足以抑制 WCF 绑定完全信任请求,因为该请求是在构造代理时以及在打开和关闭其通道时(而不是在实际使用时)发出的。通过这种方式构造代理会产生一种有趣的效果,即现在客户端代码将遵守原始的 WCF 安全请求——也就是调度对服务的调用时所需的所有安全请求!
例如,如果代理使用 TCP 绑定,则代理首先需要请求客户端权限然后才能执行(所有托管代码都需要该权限)。第二,代理要求该客户端具有连接到服务器上所需端口的权限,以及不受限制的 DNS 权限(用于解析主机地址)。第三,有一些与 TCP 的使用无关而与调用上下文相关的间接权限请求。
如果客户端希望使用 Windows 安全机制并发送用户的交互式身份信息,则代理将请求访问 USERNAME 变量的环境权限。如果客户端希望发送另一种 Windows 凭据,则代理将请求安全权限来控制主体。如果客户端希望异步调度调用或接收双向回调,则代理将请求控制策略和证明的权限(二者都是安全权限的标记,在线程间切换调用时会需要它们)。如果客户端希望使用可靠的消息传递,则代理也将请求策略控制权限。如果客户端使用的消息安全机制带有用户名凭据、带有服务证书协商但未验证协商后的服务证书,则代理也将请求权限以控制策略和证明。如果客户端利用 WCF 诊断和跟踪功能,则代理将请求访问 COMPUTERNAME 环境变量(以便能够进行跟踪)和非托管代码访问(假定访问日志文件,因此它应该已有文件 I/O 权限)。
最后,代理还将请求客户端权限以执行非托管代码。非托管代码访问是高级特权安全权限,仅授予最可信的代码。授予非托管代码访问权限可能等价于禁用 CAS,因为非托管代码在 CAS 中被豁免。设计用于在部分信任环境中工作的类和框架在任何情况下都不应该请求非托管代码访问(即使它们使用互操作功能),相反,它们应该请求更严格的权限来描述所执行的非托管操作的本性。TCP 通道请求非托管代码的原因很简单,就是因为它在最初设计的时候没有考虑到部分信任的客户端的情况。
即使能够与某些权限类型完全匹配,某些 WCF 功能还是完全依赖于完全信任的请求。任何传播事务的尝试都会请求完全信任(而不使用分布式事务权限),而任何对证书存储区的访问都需要完全信任(而不是使用证书存储区权限)。图 3 显示了在一些重要场合(例如事务、可靠性、诊断、异步调用、证书存储区访问以及消息安全性)里,当绑定被设置为其默认设置时,代码(例如我编写的上一版本的 PartialTrustClientBase<T>)所调用的原始 WCF 安全请求。

Figure 3 原始 WCF 客户端安全请求
| 方案 |
权限 |
| TCP |
执行和非托管代码安全权限、不受限制的 DNS、连接到目标主机端口的套接字权限 |
| IPC |
执行、非托管代码、控制策略以及控制证明等安全权限 |
| WS 和 WS Dual |
执行和非托管代码安全权限、连接到 URI 的 Web 权限 |
| Basic 和 Web |
执行安全权限、连接到 URI 的 Web 权限 |
| MSMQ |
执行和非托管代码安全权限 |
| 异步调用、通过 TCP 的双工通信 |
用于控制策略和证明的安全权限 |
| 基于 TCP 的 RM |
用于控制策略的安全权限 |
| 交互式用户凭据的 Windows 安全性 |
读取 USERNAME 的环境权限 |
| 替代凭据的 Windows 安全性 |
用于控制主体的安全权限、读取 USERNAME 的环境权限 |
| 诊断跟踪 |
非托管代码安全权限、读取 COMPUTERNAME 的环境权限 |
| 带有服务证书协商而不带服务证书验证的用户名凭据、消息安全性 |
用于控制策略和证明的安全权限、枚举证书的存储区权限 |
| 带有服务证书协商而不带服务证书验证的用户名凭据、TCP 消息安全性 |
用于控制策略和证明的安全权限 |
| 任何证书存储区访问、事务传播 |
完全信任 |
| 不带服务证书协商或带有服务证书验证、证书凭据的用户名凭据、消息安全性 |
完全信任 |
不请求非托管代码访问的绑定只有基本绑定和 Web 绑定。默认情况下 WS 不执行消息传递安全性的要求,从而导致请求非托管代码访问权限。
PartialTrustClientBase<T> 的结构化请求
如图 3 所示,WCF 的关键方面(例如事务、可靠消息传递、无限制的消息安全交互以及证书存储区访问)都需要完全信任,这使得我前一版本的 PartialTrustClientBase<T> 在部分信任环境中变得毫无意义。此外,通过假定所有非 HTTP 绑定(以及具有消息传递安全性的 WS 绑定)来请求非托管代码访问是不可接受的,因为这不符合 CAS 对部分信任客户端的处理方式。
为了使部分信任的客户端能适当、安全和正确地使用 WCF,PartialTrustClientBase<T> 提供了 Invoke 方法,其定义如下:
protected object Invoke(string operation,params object[] args);
Invoke 接受要调用的操作名称及其参数,格式为以逗号分隔的对象参数数组。Invoke 使用反射而不是使用 Channel 属性进行调用。以下是使用 Invoke 方法从 PartialTrustClientBase<T> 派生的代理:
public class MyContractClient :
PartialTrustClientBase<IMyContract>,IMyContract
{
public MyContractClient() {}
public MyContractClient(string endpointName) :
base(endpointName) {}
public void MyMethod() {
Invoke("MyMethod");
}
}
Invoke 首先根据调用客户端和目标服务端点的情况来请求适当的 CAS 权限。如果客户端被授予了这些权限(即请求没有产生安全异常),则 Invoke 将以编程方式声明完全信任并继续调用操作,只要客户端具有调用服务所需的正确权限。我将此类行为称为“结构化的权限请求”。
Invoke 无法使用属性来声明完全信任,因为这将屏蔽它所请求的任何更加精确的权限。相反,图 2 中 Invoke 调用的实现首先检查调用是否为异步调用(使用操作约定的 AsyncPattern 标记)。如果是,Invoke 将使用 DemandAsyncPermissions 辅助方法请求适当的权限。然后,Invoke 使用 DemandSyncPermissions 辅助方法请求同步权限。对于调用本身而言,Invoke 使用我的 CodeAccessSecurityHelper 类通过编程方式声明完全信任,如图 4 所示。

Figure 4 CodeAccessSecurityHelper 类
public enum StandardPermissionSet {
Internet,
LocalIntranet,
FullTrust,
Execution,
SkipVerification
}
public static class CodeAccessSecurityHelper {
public static PermissionSet PermissionSetFromStandardSet(
StandardPermissionSet standardSet);
public static void DemandClientPermissions<T>(this ClientBase<T> proxy)
where T : class;
public static void DemandAsyncPermissions();
//More members
PermissionSetFromStandardSet 方法接受代表标准 .NET 权限集之一的枚举值,并返回匹配的 PermissionSet 实例(请参见图 5)。顾名思义,PermissionSet 是权限的集合,但它也可以表示超级权限集完全信任(它实际上并不是一组独立权限的集合,而更像是一个独立的权限)。PermissionSet 类支持 IStackWalk 接口,该接口允许您安装堆栈遍历修饰符,有时此类修饰符可以声明堆栈中的每个调用方都包含某些权限,以此来阻止在权限集中请求该权限。堆栈遍历修饰符将在安装它的方法返回后自动被删除。您也可以使用 PermissionSet 的静态 RevertAssert 方法显式删除它。

Figure 5 PermissionState 和 PermissionSet
public enum StandardPermissionSet {
Internet,
LocalIntranet,
FullTrust,
Execution,
SkipVerification
}
public static class CodeAccessSecurityHelper {
public static PermissionSet PermissionSetFromStandardSet(
StandardPermissionSet standardSet);
public static void DemandClientPermissions<T>(this ClientBase<T> proxy)
where T : class;
public static void DemandAsyncPermissions();
//More members
PartialTrustClientBase<T> 的 DemandAsyncPermissions 和 DemandSyncPermissions 辅助方法都使用与 CodeAccessSecurityHelper 相应的方法来执行其请求。
图 6 中的图表显示了 PartialTrustClientBase<T>.Invoke 作为所用绑定的函数所发出的结构化请求以及方案的其他方面内容(例如使用事务、可靠性、证书存储区访问、诊断、回调和异步调用等)。

Figure 6 PartialTrustClientBase
的结构化安全请求
| 方案 |
权限 |
| TCP |
执行安全权限、不受限制的 DNS、连接到目标主机端口的套接字权限 |
| IPC |
执行、控制策略以及控制证明等安全权限 |
| WS、Basic |
执行安全权限以及连接到 URI 的 Web 权限 |
| WS-Dual |
执行安全权限以及连接到 URI 的 Web 权限、接受对回调地址进行回调的 Web 权限、最小化 ASP.NET 托管权限 |
| MSMQ |
执行安全权限、发送到队列的 MSMQ 权限 |
| 基于 TCP 的 RM |
用于控制策略的安全权限 |
| 通过 TCP 进行双工通信、异步调用 (AsyncPattern)、用户名凭据、带有服务证书协商而不带服务证书验证的消息安全性 |
用于控制策略和控制证明的安全权限 |
| 交互式用户凭据的 Windows 安全性 |
读取 USERNAME 的环境权限 |
| 替代凭据的 Windows 安全性 |
用于控制主体的安全权限 |
| 事务传播 |
不受限制的分发事务权限 |
| 不带服务证书协商或带有服务证书验证、证书凭据的用户名凭据、消息安全性 |
用于枚举存储区、打开存储区以及枚举证书的存储区权限 |
| 诊断跟踪 |
用于读取 COMPUTERNAME 的环境权限、用于路径发现、附加、写入日志文件的文件 I/O 权限 |
分析 Invoke 的请求
我在发出结构化请求时主要基于以下几个要素。第一,我会尽可能尝试向在客户端上由 WCF 发起的原始请求靠拢。这就是说,由于 WCF 并非是为了全面的部分信任用途而设计,所以我需要根据实际情况对其进行改动。图 6 中列出的绑定和方案都不需要请求完全信任或非托管代码访问。.NET Framework 中提供了各种方法,可以在部分信任环境的相似上下文中使用,而且我依赖于与它们相同的请求。最后,在其他一些情况下,为抵消抑制完全信任请求带来的不利状况,我借助过去的经验、对 CAS 的熟悉程度和常识将 WCF 活动映射到对专用权限类型的请求。
当使用 WS-Dual 绑定时,Invoke 使用与任何其他 HTTP 绑定一样的方法请求 Web 权限以连接到目标端点。但是,为允许托管回调对象,它还需要请求最低的 ASP.NET 托管权限和 Web 权限才能接受对回调地址的调用。当使用 MSMQ 绑定时,Invoke 将请求 MSMQ 权限以便将消息发送到目标队列。
在任何将客户端事务传播到服务器的尝试中,Invoke 都可以请求不受限制的分发事务权限。在这种情况下,事务感知绑定被采用、事务流在绑定中被启用、事务流在操作级别得到允许,并且客户端中会包含环境事务。
任何尝试通过代理来访问证书存储区的行为都会触发相应的权限请求,以枚举证书存储区、打开存储区以及枚举存储区中的证书。出现这种情况的场合包括当客户端使用证书凭据时、当使用了消息安全且客户端需要验证协商服务证书时,或者当客户端不协商证书而是改为仅加载用于保护消息安全的证书时。
当客户端使用 WCF 诊断时,Invoke 将请求用于读取计算机名称的环境权限和用于记录并跟踪文件使用情况的文件 I/O 权限。
实现客户端结构化请求
如前所述,请求是通过 CodeAccessSecurityHelper 执行的,它的部分实现如图 7 所示。.NET Framework 中的所有权限类型都支持 IPermission 接口:

Figure 7 实现 CodeAccessSecurityHelper(部分)
public static class CodeAccessSecurityHelper
{
public static void DemandClientPermissions<T>(this ClientBase<T> proxy,
string operationName) where T : class
{
DemandClientConnectionPermissions(proxy.Endpoint);
DemandTransactionPermissions(proxy.Endpoint,operationName);
DemandTracingPermissions();
DemandClientSecurityPermissions(proxy);
DemandEnvironmentPermissions(proxy);
DemandClientStorePermissions(proxy.Endpoint);
}
internal static void DemandClientConnectionPermissions(
ServiceEndpoint endpoint)
{
PermissionSet connectionSet = new PermissionSet(PermissionState.None);
if(endpoint.Binding is NetTcpBinding)
{
connectionSet.AddPermission(new SocketPermission(
NetworkAccess.Connect,TransportType.Tcp,
endpoint.Address.Uri.Host,endpoint.Address.Uri.Port));
connectionSet.AddPermission(new DnsPermission(
PermissionState.Unrestricted));
}
/* Rest of the bindings */
connectionSet.Demand();
}
internal static void DemandTransactionPermissions(
ServiceEndpoint endpoint)
{
DemandTransactionPermissions(endpoint,null);
}
internal static void DemandTransactionPermissions(
ServiceEndpoint endpoint, string operationName)
{
bool transactionFlow = false;
bool flowOptionAllowed = false;
if(endpoint.Binding is NetTcpBinding)
{
NetTcpBinding tcpBinding = endpoint.Binding as NetTcpBinding;
transactionFlow = tcpBinding.TransactionFlow;
}
/* Checking other bindings */
if(transactionFlow)
{
if(Transaction.Current != null)
{
//If operationName is null then at least one operation
//needs to allow flow to trigger demand
foreach(OperationDescription operation in
endpoint.Contract.Operations)
{
string name = operationName ?? operation.Name;
if(name != operation.Name)
{
continue;
}
foreach(IOperationBehavior behavior in operation.Behaviors)
{
if(behavior is TransactionFlowAttribute)
{
TransactionFlowAttribute attribute =
behavior as TransactionFlowAttribute;
if(attribute.Transactions !=
TransactionFlowOption.NotAllowed)
{
flowOptionAllowed = true;
break;
}
}
}
if(flowOptionAllowed)
{
break;
}
}
if(flowOptionAllowed)
{
IPermission distributedTransactionPermission =
new DistributedTransactionPermission(
PermissionState.Unrestricted);
distributedTransactionPermission.Demand();
}
}
}
}
//Rest of the implementation
}
public interface IPermission : ...
{
void Demand();
//More members
}
要请求任何权限,可实例化权限对象并调用其 Demand 方法的实现。构造权限集时,可以为其添加单独的权限,然后调用该权限集的 Demand 方法来请求其中的所有权限。
CodeAccessSecurityHelper 的扩展方法 DemandClientPermissions 代表 PartialTrustClientBase<T> 执行批量请求(使用的扩展可用于任何代理类)。它包含一系列辅助方法,它们都可以分别请求某方面的权限。图 7 显示了 DemandClientConnectionPermissions 的代码,它用于根据绑定来请求连接权限。它通过代理检查所使用的绑定类型,并向权限集对象中添加适当的权限。然后,它将调用该权限集的 Demand 方法。
DemandTransactionPermissions 方法首先核实代理是否正在使用事务流绑定功能。如果是,它还将核实调用客户端是否真的包含要传播的环境事务(Transaction.Current 不为空)。如果是,它将扫描该端点约定中操作的集合,以寻找当前调用的操作。找到操作后,DemandTransactionPermissions 将检索该操作的操作行为集合。DemandTransactionPermissions 将检查每种行为,寻找 TransactionFlowAttribute。如果该属性被配置为允许事务传播,则 DemandTransactionPermissions 会请求分发的事务权限。DemandClientPermissions 还可通过相似的方式使用其他辅助方法请求适当的权限。
最后,为启用部分信任的客户端和回调,我定义了 PartialTrustDuplexClientBase<T,C> 类,并按照与 PartialTrustClientBase<T> 非常相似的方法实现了它,不同之处在于它为客户端接收回调加入了双工支持。
至此,我们已经快速介绍了 WCF 中的代码访问安全,还介绍了可启用部分信任的客户端并具有相应代码级权限级别的解决方案。我将在下一部分中详细介绍部分信任的服务和部分信任的宿主。您还将了解到一些 WCF 和 .NET Framework 的高级编程技术。
请将您想询问的问题和提出的意见发送至 mmnet30@microsoft.com.
Juval Lowy 是 IDesign 的一名软件架构师,提供 WCF 培训和 WCF 体系结构咨询服务。另外,他还是 Microsoft 硅谷地区的区域总监。他最近编写了一本名为《Programming WCF Services》
的新书。您可以通过
www.idesign.net 与 Juval 联系。