可打印版本       提交     
请单击以进行评分并提供反馈
Related Articles

内存使用量直接影响到应用程序的执行速度,因此优化内存非常重要。在本文中,我们将介绍有关 .NET 程序内存优化的基本知识。

Subramanian Ramaswamy 和 Vance Morrison

MSDN Magazine 六月 2009

...

Read more!

结合使用测试驱动开发和模拟对象来根据角色和职责(而不是类层次结构中的对象分类)设计面向对象的代码。

Isaiah Perumalla

MSDN Magazine 六月 2009

...

Read more!

开发人员经常会遇到版本控制工作流及其相关类方面的难题。Matt Milner 将介绍与工作流版本控制相关的核心问题并为更改工作流定义、活动和工作流服务提供建议。

Matthew Milner

MSDN Magazine May 2009

...

Read more!

在本文中,作者将剖析 ASP.NET MVC 框架并介绍控制器的工作方式。然后,他还将介绍框架与控制器的交互方式以及您如何可以影响这些交互方式。

Scott Allen

MSDN Magazine May 2009

...

Read more!

.NET RIA Services 提供了一套服务器组件,并提供了 ASP.NET 扩展,如身份验证、角色和配置文件管理。我们将向您介绍它们的工作原理。

Jonathan Carter

MSDN Magazine May 2009

...

Read more!

Also by this Author

While .NET offers adequate support for the simple serialization cases, issues arise when you're attempting to support delegates and subscribers, versioning, and class hierarchies. . In addition, the introduction of generics in The .NET Framework breaks new ground in the complexity of the serialization task and the power of serialization tools. This article discusses these issues and related pitfalls, suggests techniques and workarounds that address them, and recommends when and how to best use the new serialization abilities.

Juval Lowy

Read more!

Leasing and sponsorship is the solution for managing the lifecycle of a remote object in .NET. Each object has a lease that prevents the local garbage collector from destroying it, and most distributed applications rely upon leasing. There are several ways in which objects and clients can extend the lease, including dedicated sponsor objects. In this article, the author explains leasing, shows how to configure it, and how it relates to the various remoting activation models. He then discusses design guidelines and options, along with their impact ...

Read more!

Juval Lowy 为基于 Windows Communication Foundation 构建的应用程序设计了可轻松配置的安全设置。

Juval Lowy

MSDN Magazine August 2007

...

Read more!

Object and component-oriented programming have only one way for clients to call a method, but Windows® Communication Foundation introduces two more. In this article Juval Lowy explains how they work.

Juval Lowy

MSDN Magazine October 2006

...

Read more!

在本文中,Juval Lowy 介绍了 Visual Studio 2005 中令人兴奋的新增功能,与第一版 C# 相比,这些功能可提高总体生产力,以便您可以更快地编写更为简洁的代码。

Juval Lowy

MSDN Magazine Visual Studio 2005 Guided Tour 2006

...

Read more!

Popular Articles

现在,您可以在 SQL Server 2005 中使用正则表达式执行高效且复杂的文本分析。

David Banister

MSDN Magazine February 2007

...

Read more!

本文介绍了有关编程式和声明性数据绑定和使用 Windows Presentation Foundation 进行显示的技术。

Josh Smith

MSDN Magazine 7 月 2008

...

Read more!

Kenny Kerr 高度赞扬了新的 Visual C++ 2008 功能包,称它为 Visual C++ 带来了不同寻常的便利。

Kenny Kerr

MSDN Magazine May 2008

...

Read more!

Ray Djajadinata

MSDN Magazine May 2007

...

Read more!

Jason Clark

MSDN Magazine July 2003

...

Read more!

基础
向 WCF 新增代码访问安全性,第 2 部分
Juval Lowy

代码下载位置: Foundations2008_07.exe (497 KB)
在线浏览代码

在 2008 年 4 月刊的这一专栏中,我讨论了缺少对代码访问安全性 (CAS) 足够支持的情形,以及向 Windows® Communication Foundation (WCF) 添加合适 CAS 支持的动机。然后展示了如何在客户端添加该支持,从而使部分受信的客户端可以调用 WCF 服务,并且它们均不会损害安全性或 WCF 编程模型(如需四月专栏,请参阅 msdn.microsoft.com/magazine/cc500644)。
本期我将接着向您介绍部分受信的服务和宿主,帮您形成完整的印象。与客户端一样,我讲解的内容不仅包含解决方案的机制,还有它的形成方法和构思过程。您还将了解到一些 WCF 和 Microsoft® .NET Framework 的高级编程技术。

.NET Framework 3.5 中的宿主端 CAS
在 .NET Framework 3.5 中,WCF 仅允许部分受信的代码通过 BasicHttpBinding、WSHttpBinding 和 WebHttpBinding 绑定托管服务,并且不提供任何安全性或仅提供传输安全性。另外,对于 WSHttpBinding,消息安全性、可靠的消息传递以及事务等都被禁用。所有启用部分受信的绑定都必须使用文本编码。在部分受信模式下运行的服务无法使用其他功能(如诊断和性能计数器)。
由绑定负责强制执行某些支持功能。每个非 HTTP 绑定都会主动要求服务宿主的完全信任。适用 HTTP 绑定本身不需要完全信任;而是需要根据使用环境确定所需的权限(如图 1 所示)。
方案 权限
Basic、Web 和 WS – 没有安全性或传输安全性 执行和基础结构安全权限;接受 URI 调用的 Web 权限。
前面提到的绑定,且在单独程序集中包含内部服务类型 不受限制的反射。
前面提到的绑定,且包含通过身份验证的调用 用于控制主体的安全权限。
所有适用 HTTP 绑定均需要执行权限和修改基础结构的权限(带有基础结构标记的安全权限),以及对其端点 URI 调用的接受权限(带有 URI 接受标记的 Web 权限)。调用经过验证后,适用 HTTP 绑定还需要用于控制线程主体的权限(带有主体控制标记的安全权限)。
这一点可以满足,原因是在身份验证之后,无论是何种绑定,WCF 都会安装一个新的主体,且其身份标识与调用期间提供的客户端凭据相匹配。如果向宿主构造函数提供的服务类型定义为另一程序集的内部类型,宿主还需要不受限制的反射权限,以便能使用反射加载该类型。(需要基础结构权限的决定有些莫明其妙,因为并无对它的明显需求。)
还需要考虑其他一些对配置的限制。例如,.config 文件不能包含任何对服务端证书存储区的引用,因为访问证书存储区会使 WCF 请求完全信任权限。管理员必须使用工具(如 HttpConfig.exe)单独配置这些证书。
仅依靠绑定请求宿主端权限的不足之处是:无论需要与否,都会隐式赋予托管服务实例这些权限。例如,服务实例可控制主体或反射宿主上其他程序集的内部类型。如果在确保服务权限为宿主代码权限子集的同时,将绑定设计为请求其权限并让宿主能在一定程度上向服务授予不同权限,效果会更好。
理想情况下,您会希望能够领略 WCF 的全部功能,从分布式事务到可靠调用、各种安全性凭据类型、具有 TCP 和进程间通信(命名管道)绑定的 intranet(甚至是同一台计算机)应用程序;双向回调、异步调用、诊断和跟踪、检测,当然还有通过 Microsoft 消息队列 (MSMQ) 实现的排队调用。并且您希望所有这一切都不会损害 CAS(即不必依赖完全信任权限实现上述功能)。

部分受信的服务
在 .NET Framework 3.0 中,要想用部分信任的形式执行服务,唯一方法是明确授予服务履行功能必需的权限,隐式拒绝所有其他权限。通过 SecurityAction.PermitOnly 标记应用相应的权限属性能够做到这一点。请考虑以下代码中的服务:
[SecurityPermission(
  SecurityAction.PermitOnly,
  Execution = true)]
[UIPermission(SecurityAction.PermitOnly,
  Window = 
    UIPermissionWindow.SafeTopLevelWindows)]
class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}
服务需要执行权限(与所有受管代码一样),并且还必须具有显示安全窗口的权限。如果在一个类上堆放多个必需权限属性,在运行时它会产生单个权限集。
通过安装专用的堆栈遍历修饰符,.NET Framework 使用它仅授予这些权限并主动拒绝所有其他权限。结果是,即使服务所在的程序集(以及应用程序域)向其授予完全信任权限,也会拒绝所有其他权限。
当服务尝试执行一个操作(如打开某个文件)时,由于文件 I/O 请求会遇到将主动拒绝该权限的堆栈遍历修饰符,所以会触发安全异常。服务能做的就是在虚拟沙箱中执行并显示安全窗口。.NET Framework 将更改窗口标题并在出现的窗体中显示一个警告标记,告诉用户应用程序为部分受信程序(如图 2 所示)。
图 2 部分受信代码显示的窗口(单击图像可查看大图)
您不是将权限指定为属性,而是将它们列在权限集 XML 文件中并将该文件名称提供给 PermissionSetAttribute(如图 3 所示)。
<!-- MyServicePermissions.xml --> 
<PermissionSet class = "System.Security.PermissionSet">
  <IPermission 
    class = "System.Security.Permissions.SecurityPermission" 
    Flags = "Execution"
  />
  <IPermission 
    class = "System.Security.Permissions.UIPermission" 
    Window = "SafeTopLevelWindows"
  />
</PermissionSet>

[PermissionSet(SecurityAction.PermitOnly,File = 
  "MyServicePermissions.xml")]
class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}
请注意,权限集文件仅在编译时使用,并不用于部署。编译器将把允许的权限融入类元数据,作为单个属性使用。如果文件并不存在,构建将失败。

App Domain Host
通过基于属性的方法实现部分受信服务存在许多问题。首先,每次服务内容发生更改(或它使用的任意下游类发生内容更改)时,均需要修改属性堆栈或更改文件。这样会将服务耦合到这些类并增加维护成本。
其次,由于权限是服务定义的一部分,因而无法在托管环境中产生具有不同权限的服务。无法赋予其更多或更少的权限。
第三,也是更为重要的一点,在实际中,不太可能出现大部分服务费力分析其运行所必需的安全权限,并刻意降低其拥有的权限,以便采用最低权限运行。如果有,那就是应由宿主考虑它加载的服务能胜任何种工作,而默认情况下宿主是无法影响这一点的。WCF 的 ServiceHost 将托管完全信任的所有服务。
出于宿主以及服务自身保护的考虑,允许宿主授予其认为适合于服务的权限是更好的选择。为此,我编写了图 4 中定义的类 AppDomainHost。
public class AppDomainHost : IDisposable {
  //Create new app domain in full trust
  public AppDomainHost(Type serviceType,params Uri[] baseAddresses);
  public AppDomainHost(Type serviceType,string appDomainName,
    params Uri[] baseAddresses);

  //Create new app domain with specified permission set
  public AppDomainHost(Type serviceType,PermissionSet permissions,
    params Uri[] baseAddresses);
  public AppDomainHost(Type serviceType,PermissionSet permissions,
    string appDomainName,params Uri[] baseAddresses);

  //Additional constructors that take standard permission set, 
  //permission set filename, and an existing app domain  

  public void Open();
  public void Close();
  public void Abort();

  //More members 
}
类 AppDomainHost 的用法与 WCF 提供的 ServiceHost 一样:
AppDomainHost host = 
  new AppDomainHost(typeof(MyService));
host.Open();
不同之处在于 AppDomainHost 将把提供的服务类型放在一个新的应用程序域中,而非放在其调用方的应用程序域中。新应用程序域名将默认为 "AppDomain Host for",后面带有服务类型和一个新的 GUID。也可为新应用程序域指定名称:
AppDomainHost host = 
  new AppDomainHost(typeof(MyService),"My App Domain");
host.Open();
默认情况下,以完全信任级别创建新的应用程序域。然而,将服务放在一个单独的应用程序域中是管理部分受信服务的关键所在。
AppDomainHost 允许为新应用程序域提供权限。例如,图 5 展示了服务和仅向服务授予正常运行所需权限的宿主。
class MyService : IMyContract {
  public void MyMethod() {
    Form form = new TestForm();
    form.ShowDialog();
  }
}

//Hosting code:
PermissionSet permissions = 
  new PermissionSet(PermissionState.None);
permissions.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));
permissions.AddPermission(new UIPermission(
  UIPermissionWindow.SafeTopLevelWindows));

AppDomainHost host = 
  new AppDomainHost(typeof(MyService),permissions);

host.Open();
您也可使用权限集文件,与图 3 中所示不同,此文件仅在运行时才会用到并且可在部署后进行修改。请特别注意的是,还可以指定一个标准命名权限集。利用部分受信服务的虚拟沙箱会影响可能使用的所有下游类,这些类是它能在沙箱外通过任意技术(从 .NET Framework 1.0 到 WCF)调用的。
单个宿主进程可拥有 AppDomainHost 的多个实例,每个实例托管相同或不同的服务类型,每个服务类型又具有任意的权限集,如图 6 所示(使用与图 5 中相同的服务定义)。
//Default is service with full trust
AppDomainHost host0 = 
  new AppDomainHost(typeof(MyService),
  "Full Trust App Domain",
  new Uri("net.tcp://localhost:6000"));

host0.Open();

//With just enough permissions to do work
PermissionSet permissions1 = 
  new PermissionSet(PermissionState.None);
permissions1.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));
permissions1.AddPermission(new UIPermission(
  UIPermissionWindow.SafeTopLevelWindows));

AppDomainHost host1 = 
  new AppDomainHost(typeof(MyService),permissions1,
  "Partial Trust App Domain",
  new Uri("net.tcp://localhost:6001"));

host1.Open();

//With not enough permissions to do work
PermissionSet permissions2 = 
  new PermissionSet(PermissionState.None);
permissions2.AddPermission(new SecurityPermission(
  SecurityPermissionFlag.Execution));

AppDomainHost host2 = 
  new AppDomainHost(typeof(MyService),permissions2,
  "Not enough permissions",new Uri("net.tcp://localhost:6002"));

host2.Open();

//Using one of the named permission sets
AppDomainHost host3 = 
  new AppDomainHost(typeof(MyService),
  StandardPermissionSet.Internet,
  "Named permission set",
  new Uri("net.tcp://localhost:6003"));

host3.Open();

实现 AppDomainHost
实现 AppDomainHost 分为两个部分:创建一个新的应用程序域并注入一个服务宿主实例,然后在部分信任环境下执行服务宿主(和服务实例)。为创建一个服务宿主实例并在单独的应用程序域中激活它,我编写了图 7 中所示的类 ServiceHostActivator。
class ServiceHostActivator : MarshalByRefObject {
  ServiceHost m_Host;

  public void CreateHost(Type serviceType,Uri[] baseAddresses) {
    m_Host = new ServiceHost(serviceType,baseAddresses);
  }
  public void Open() {
    m_Host.Open();
  }   
  public void Close() {
    m_Host.Close();
  }
  public void Abort() {
    m_Host.Abort();
  }

  //Rest of the implementation
}
ServiceHostActivator 是 WCF 提供的标准 ServiceHost 实例的一个简单包装。ServiceHostActivator 从 MarshalByRefObject 派生而来,因此 AppDomainHost 可跨越应用程序域边界调用它。CreateHost 方法封装构建一个新 ServiceHost 实例。ServiceHostActivator 方法的剩余部分只是将远程调用转发到底层宿主实例。
AppDomainHost 提供多个重载构造函数。这些构造函数均彼此调用(请参见图 8),甚至创建一个新的应用程序域并最终使用受保护的构造函数(它会使用服务类型、新的应用程序域实例、新域的权限集以及基址)结束构造。
public class AppDomainHost : IDisposable {
  ServiceHostActivator m_ServiceHostActivator;

  public AppDomainHost(Type serviceType,
    params Uri[] baseAddresses) :  
    this(serviceType,"AppDomain Host for "+
    serviceType+" "+Guid.NewGuid(),
    baseAddresses) {
  }

  public AppDomainHost(Type serviceType,
    string appDomainName,
    params Uri[] baseAddresses) : this(serviceType,
    new PermissionSet(PermissionState.Unrestricted),
    appDomainName,baseAddresses) {
  }

  public AppDomainHost(Type serviceType,
  PermissionSet permissions,
  string appDomainName,
  params Uri[] baseAddresses) : 
  this(serviceType,AppDomain.CreateDomain(appDomainName),
  permissions,baseAddresses) {
  }

  //More constructors 

  protected AppDomainHost(Type serviceType,
    AppDomain appDomain,
    PermissionSet permissions,Uri[] baseAddresses) {

    string assemblyName = Assembly.GetAssembly(
      typeof(ServiceHostActivator)).FullName;
    m_ServiceHostActivator = appDomain.CreateInstanceAndUnwrap(
      assemblyName,typeof(ServiceHostActivator).ToString()) as 
      ServiceHostActivator;

    CodeAccessSecurityHelper.SetPermissionsSet(appDomain,permissions);

    m_ServiceHostActivator.CreateHost(serviceType,baseAddresses);
  }

  public void Open() {
    m_ServiceHostActivator.Open();
  }
  public void Close() {
    m_ServiceHostActivator.Close();
  }
  public void Abort() {
    m_ServiceHostActivator.Abort();
  }
  void IDisposable.Dispose() {
    Close();
  }
}
AppDomainHost 的受保护构造函数使用 .NET 远程机制向新应用程序域注入一个 ServiceHostActivator 实例,并且最后向其注入存储在 m_ServiceHostActivator 成员中的远程机制代理。
默认情况下,以完全信任级别创建新的应用程序域。AppDomainHost 使用 CodeAccessSecurityHelper 类的 SetPermissionsSet 方法在新的应用程序域中安装一个新的 CAS 策略 — 从而仅授予提供的权限并拒绝其他权限:
public static class CodeAccessSecurityHelper {
  public static void SetPermissionsSet(
    AppDomain appDomain,
    PermissionSet permissions) {

    PolicyLevel policy = PolicyLevel.CreateAppDomainLevel();
    policy.RootCodeGroup.PolicyStatement = 
      new PolicyStatement(permissions);
    appDomain.SetAppDomainPolicy(policy);
  }
  //More members
}
它与在应用程序域级创建一个新的安全策略并调用 AppDomain 类的 SetAppDomainPolicy 方法一样简单:
public sealed class AppDomain : 
  MarshalByRefObject,... {

  public void SetAppDomainPolicy(
    PolicyLevel domainPolicy);
  //More members
}
顺便说一下,ClickOnce 使用类似技术针对部署了 ClickOnce 的应用程序来强制执行部分信任。
如果调用 AppDomainHost 的其他方法(如 Open 或 Close),将使用 ServiceHostActivator 的代理来调用其他应用程序域并让其打开或关闭宿主实例。由于服务实例将在打开宿主的应用程序域中执行,因此服务执行时还会使用应用程序域的安全策略。

部分受信的宿主
迄今为止,实现部分受信服务的方法是有前提的,即拥有代码且它使用以完全信任权限运行的 AppDomainHost。原因是除图 1 中列出的托管方案以外,ServiceHost 和绑定都需要请求完全信任。
但如果启动宿主的仅是部分受信的代码,那又会如何呢?可将 AppDomainHost 和 ServiceHostActivator 放入完全受信的程序集中,允许有部分受信的调用方并将它们均声明为完全信任:
[PermissionSet(SecurityAction.Assert,Unrestricted = true)]
public class AppDomainHost : IDisposable
{...}

[PermissionSet(SecurityAction.Assert,Unrestricted = true)]
class ServiceHostActivator : MarshalByRefObject
{...}
尽管此方法很有效,但它避开了 CAS 并禁用了至关重要的安全机制,并且这样会产生两个安全性问题。首先,不应假设打开宿主的代码有权接受针对传输通道的调用或能自由参与 WCF 活动(如分发事务)。其次,通过声明完全信任且禁止堆栈遍历,启动宿主的部分受信代码实际可创建一个具有更高权限的服务,将其用于不良企图。例如,托管代码可能并无文件 I/O 权限,但它可使用 AppDomainHost 接受对具有文件 I/O 权限的服务的调用。
解决方案很简单:AppDomainHost 和 ServiceHostActivator 不应使用完全信任的空白声明。而是应在必要时仅在本地声明,然后还原成现有权限。此外,AppDomainHost 应能质询使用它的代码,请求适当的托管权限(如接受调用)。它还应确认托管代码至少已被授予运行服务所需的权限。从而防止部分信任的受限代码以更大的权限使用在单独应用程序域中托管的服务。最终产生出结构化宿主端安全要求。

结构化宿主端安全要求
图 9 显示了适用于部分信任的调用方且经过改写的 ServiceHostActivator。ServiceHostActivator 的 CreateHost 方法无法声明完全信任,因为这样将使其无法请求调用代码的适当宿主权限。它通过编程方式使用 CodeAccessSecurityHelper 创建完全信任权限集并加以声明,然后继续创建宿主。当 WCF 请求完全信任时,声明可防止该请求在调用堆栈中上移。
class ServiceHostActivator : MarshalByRefObject {
  public void CreateHost(Type serviceType,Uri[] baseAddresses) {
    CodeAccessSecurityHelper.PermissionSetFromStandardSet(
    StandardPermissionSet.FullTrust).Assert();

    m_Host = new ServiceHost(serviceType,baseAddresses);

    PermissionSet.RevertAssert();

    m_Host.DemandHostPermissions();
  }

  //Behavior demands happen here, must assert 
  [PermissionSet(SecurityAction.Assert,Unrestricted = true)]
  public void Open() {
    m_Host.Open();
  }   
  ...
}
创建宿主后,CreateHost 明确还原声明。然后,CreateHost 针对刚创建的宿主实例调用 DemandHostPermissions(CodeAccessSecurityHelper 的扩展帮助程序方法)以请求托管权限。DemandHostPermissions 检查宿主对象并请求适当的权限。
例如,如果宿主支持 TCP 端点,则 DemandHostPermissions 会请求执行权限和接受对端点 URI 的 TCP 调用的权限。如果通过 TCP 使用可靠的消息传递,DemandHostPermissions 会请求控制策略权限。还可能有许多其他请求,如图 10 所示。
方案 权限
TCP 执行安全权限、接受对 URI 的 TCP 调用的套接字权限。
IPC 执行、控制策略以及控制证明等安全权限。
MSMQ 执行安全权限、从队列读取的 MSMQ 权限。
WS、WS-Dual、Basic、Web 执行安全权限和接受对 URI 的调用的 Web 权限。
基于 TCP 的 RM 用于控制策略的安全权限。
通过身份验证的调用 用于控制主体的安全权限。
事务传播 不受限制的分发事务权限
具有消息安全性的用户名凭据、具有验证的证书凭据以及服务证书 用于枚举存储区、打开存储区以及枚举证书的存储区权限
诊断跟踪 用于读取 COMPUTERNAME 的环境权限、用于路径发现、附加和写入日志文件的文件 I/O 权限。
WCF 服务性能计数器 向服务计数器、端点计数器和操作计数器写入的性能计数器权限。
所有 WCF 性能计数器 向服务计数器、端点计数器、操作计数器和宿主计数器写入的性能计数器权限。
ASP.NET 提供程序 最小 ASP.NET 托管权限。
使用 IPC 绑定时需要用于控制策略和证明的权限。使用 MSMQ 绑定时需要从端点队列读取的权限。使用任何 HTTP 绑定时都需要能够接受对端点地址的调用。如果绑定使用通过身份验证的调用,DemandHostPermissions 会请求控制主体权限。
如果使用识别事务的绑定,同时启用了事务流并允许(或强制)对事务流的端点约定至少执行一个操作,则需要不受限制的分发事务权限。
只要访问证书存储区(如通过消息安全性、客户端证书验证或使用服务证书来保护消息),就需要存储区枚举权限以打开存储区并枚举证书。
如果服务执行诊断,DemandHostPermissions 将需要访问计算机名称环境变量以及该文件的文件 I/O 权限。如果宿主使用 WCF 性能计数器,DemandHostPermissions 会请求在报告级写入计数器的权限。
如果宿主依赖于 ASP.NET 提供程序实现调用方身份验证或角色成员身份,DemandHostPermissions 将需要最小的 ASP.NET 托管权限(所有提供程序均需要该权限)。在通过 WS-Dual 绑定回调时,绑定本身将需要 Web 权限来连接该回调端点,因此在启动宿主时 AppDomainHost 不必显式请求此权限。

实现结构化宿主请求
图 11 显示了 DemandHostPermissions 的部分实现,它首先执行特定于端点的要求。它遍历服务的端点集合,并且对于每个端点,它均需要单独的连接性、安全性和事务权限。
public static class CodeAccessSecurityHelper {
  internal static void DemandHostPermissions(this ServiceHost host) {
    foreach(ServiceEndpoint endpoint in host.Description.Endpoints) {
      DemandHostConnectionPermissions(endpoint);
      DemandHostSecurityPermissions(endpoint);
      using(TransactionScope scope = new TransactionScope()) {
        DemandTransactionPermissions(endpoint);
      }
    }
    DemandHostStorePermissions(host);
    DemandPerformanceCounterPermissions();
    DemandTracingPermissions();
    DemanAspNetProvidersPermissions(host);
  }
  internal static void DemandHostConnectionPermissions(
    ServiceEndpoint endpoint) {

    PermissionSet connectionSet = 
      new PermissionSet(PermissionState.None);
    if(endpoint.Binding is NetTcpBinding) {
      connectionSet.AddPermission(new SocketPermission(
        NetworkAccess.Accept, TransportType.Tcp,
        endpoint.Address.Uri.Host,endpoint.Address.Uri.Port));
    }

    /* Checking the other bindings */

    connectionSet.Demand();
  }

  static void DemanAspNetProvidersPermissions(ServiceHost host) {
    bool demand = false;

    foreach(IServiceBehavior behavior in host.Description.Behaviors) {
      if(behavior is ServiceCredentials) {
        ServiceCredentials credentialsBehavior = 
          behavior as ServiceCredentials;

        if(credentialsBehavior.UserNameAuthentication.
          UserNamePasswordValidationMode == 
          UserNamePasswordValidationMode.MembershipProvider)  {

          demand = true;
          break;
        }
      }
      if(behavior is ServiceAuthorizationBehavior) {
        ServiceAuthorizationBehavior serviceAuthorization = 
          behavior as ServiceAuthorizationBehavior;
        if(serviceAuthorization.PrincipalPermissionMode == 
          PrincipalPermissionMode.UseAspNetRoles && 
          Roles.Enabled) {

          demand = true;
          break;
        }
      }
    }
    if(demand) {
      IPermission permission = 
        new AspNetHostingPermission(
        AspNetHostingPermissionLevel.Minimal);
      permission.Demand();
    }
  }
  //Rest of the implementation 
}
对于事务权限请求,它使用事务范围创建一个临时的环境事务。从而可使用与客户端相同的方法,该方法在调用时根据是否存在环境事务来请求权限。
端点进行请求之后,DemandHostPermissions 请求服务宿主配置产生的权限,具体说来是证书存储区访问权限、性能计数器权限、跟踪权限和 ASP.NET 提供程序权限。例如,要请求 ASP.NET 提供程序权限,它获取服务行为集合并查看宿主的服务凭据行为是否在使用成员身份提供程序,或宿主的服务授权行为是否在使用角色提供程序,如果是则请求 ASP.NET 托管权限。
还需要改写 AppDomainHost 以在部分信任环境中支持结构化请求,如下所示:
[SecurityPermission(
  SecurityAction.Assert,ControlAppDomain = true)]
[ReflectionPermission(
  SecurityAction.Assert,Unrestricted = true)]
public class AppDomainHost : IDisposable {
  protected AppDomainHost(Type serviceType,
    AppDomain appDomain,
    PermissionSet permissions,
    params Uri[] baseAddresses) {
    //Cannot grant service permissions 
    // the host does not have
    permissions.Demand();

    //Rest of constructor 
    }
}
由于 AppDomainHost 创建了一个新的应用程序域,因此要声明对此应用程序域的控制权限。因为它使用反射在其他应用程序域中注入宿主,因此要声明不受限制的反射权限。最重要的是,在新的应用程序域中创建服务宿主之前,AppDomainHost 会为调用它的代码的服务请求所需权限。从而确保部分受信的代码仅可启动并托管作用范围与调用代码相同的服务。

AppDomainHost 的其他方面
尽管 AppDomainHost 并不支持 ICommunicationObject,但它提供了事件模型和状态机管理。AppDomainHost 还将一些静态变量从调用应用程序域复制到自己创建的新应用程序域中。具体来说,它使用 ServiceHostActivator 将 ASP.NET 提供程序应用程序名称复制到其他应用程序域。AppDomainHost 临时声明 ASP.NET 托管权限、访问提供程序、复制成员身份和角色应用程序名称的值,然后还原声明。

Juval Lowy 是 IDesign 的一名软件架构师,该公司提供 WCF 培训和体系结构咨询。他最近编写了一本名为《Programming WCF Services》的新书。另外,他还是 Microsoft 硅谷地区的区域总监。您可以通过 www.idesign.net 与 Juval 联系。

Page view tracker