WCF 的委派和模拟

模拟 是一种常用技术,服务可使用该技术限制客户端对服务域资源的访问。 服务域资源可以是计算机资源,如本地文件(模拟),也可以是其他计算机上的资源,如文件共享(委托)。 有关示例应用程序,请参见 Impersonating the Client。 有关如何使用模拟的示例,请参阅 How to: Impersonate a Client on a Service

重要

请注意,在对某一服务模拟客户端时,该服务会使用客户端的凭据运行,因此可能具有高于服务器进程的权限。

概述

通常,客户端会调用某个服务,使该服务代表客户端执行某个操作。 模拟使服务可在执行操作时充当客户端。 委托使前端服务可采用某种方式将客户端的请求转发到后端服务,以使后端服务也能模拟客户端。 模拟是检查客户端是否有权执行特定操作的最常用的方法,而委托是一种使模拟功能以及客户端的标识流向后端服务的方法。 委托是一种 Windows 域功能,可在执行基于 Kerberos 的身份验证时使用。 委托不同于标识流,因为委托可传送无需拥有客户端密码即可模拟客户端的能力。与标识流相比,委托是一种权限更高的操作。

模拟和委托都要求客户端具有 Windows 标识。 如果客户端没有 Windows 标识,则唯一的选择就是使客户端的标识流向第二个服务。

模拟基础知识

Windows Communication Foundation (WCF) 支持各种客户端凭据的模拟。 本主题说明在实现某一服务方法的过程中支持模拟调用方的服务模型。 还讨论了其中涉及模拟以及 SOAP 安全和 WCF 选项的常见部署方案。

本主题重点介绍使用 SOAP 安全时 WCF 中的模拟和委托。 你也可以在使用传输安全性时将模拟和委托与 WCF 一起使用,如将模拟用于传输安全性中所述

两种方法

对于模拟的执行,WCF SOAP 安全有两种不同的方法。 所用的方法取决于绑定。 一种方法是从 Windows 令牌模拟,此令牌从安全支持提供程序接口 (SSPI) 或 Kerberos 身份验证获取,然后在服务上缓存。 另一种方法是从 Windows 令牌模拟,此令牌从 Kerberos 扩展获取,统称为“Service-for-User” (S4U)。

缓存的令牌模拟

您可以对以下各项执行缓存的令牌模拟:

基于 S4U 的模拟

您可以对以下各项执行基于 S4U 的模拟:

  • 使用证书客户端凭据(服务可以将该凭据映射到有效的 Windows 帐户)的WSHttpBinding, WSDualHttpBindingNetTcpBinding

  • 使用 Windows 凭据并且 CustomBinding 属性设置为 requireCancellation 的任何 false

  • 使用用户名或 Windows 客户端凭据和安全对话,并且 CustomBinding 属性设置为 requireCancellation 的任何 false

服务可以模拟客户端的范围取决于服务帐户在尝试模拟时拥有的权限、使用的模拟类型和客户端可能允许的模拟范围。

备注

当客户端和服务在同一计算机上运行,并且客户端在系统帐户(例如 Local SystemNetwork Service)下运行时,如果安全会话是使用有状态安全上下文令牌建立的,则不能模拟客户端。 Windows 窗体或控制台应用程序通常在当前登录的帐户下运行,因此默认情况下可以模拟该帐户。 但是,如果客户端为 ASP.NET 页面,并且该页面在 IIS 6.0 或 IIS 7.0 中托管,则默认情况下,客户端会在 Network Service 帐户下运行。 默认情况下,系统提供的所有支持安全会话的绑定都使用无状态安全上下文令牌 (SCT)。 但如果客户端为 ASP.NET 页面,并且使用了具有有状态 SCT 的安全会话,则无法模拟该客户端。 有关在安全会话中使用有状态 SCT 的详细信息,请参阅如何:为安全会话创建安全上下文令牌

服务方法中的模拟:声明性模型

大多数模拟方案都涉及在调用方上下文中执行服务方法。 WCF 提供了一种模拟功能,此功能通过允许用户在 OperationBehaviorAttribute 属性中指定模拟要求,使此操作易于执行。 例如,在下面的代码中,WCF 基础结构在执行 Hello 方法之前模拟调用方。 只有当本机资源的访问控制列表 (ACL) 允许调用方访问权限时,在 Hello 方法内访问该本机资源的任何尝试才能成功。 若要启用模拟,请将 Impersonation 属性设置为 ImpersonationOption 枚举值之一( ImpersonationOption.RequiredImpersonationOption.Allowed),如下面的示例所示。

备注

当服务具有比远程客户端更高的凭据时,在 Impersonation 属性设置为 Allowed的情况下将使用服务的凭据。 也就是说,如果低权限的用户提供其凭据,则较高权限的服务会使用服务的凭据执行该方法,并可以使用低权限的用户不能使用的资源。

[ServiceContract]
public interface IHelloContract
{
    [OperationContract]
    string Hello(string message);
}

public class HelloService : IHelloService
{
    [OperationBehavior(Impersonation = ImpersonationOption.Required)]
    public string Hello(string message)
    {
        return "hello";
    }
}

<ServiceContract()> _
Public Interface IHelloContract
    <OperationContract()> _
    Function Hello(ByVal message As String) As String
End Interface


Public Class HelloService
    Implements IHelloService

    <OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
    Public Function Hello(ByVal message As String) As String Implements IHelloService.Hello
        Return "hello"
    End Function
End Class

只有当调用方使用可以映射到 Windows 用户帐户的凭据进行身份验证时,WCF 基础结构才能模拟调用方。 如果将服务配置为使用无法映射到 Windows 用户帐户的凭据进行身份验证,则不会执行服务方法。

备注

在 Windows XP 中,如果创建了有状态 SCT,则模拟会失败,并引发 InvalidOperationException。 有关详细信息,请参阅不支持的方案

服务方法中的模拟:命令性模型

有时,调用方不需要模拟整个服务方法,只需模拟它的一部分就行。 在这种情况下,请获取服务方法中调用方的 Windows 标识并以强制方式执行模拟。 为此,请使用 WindowsIdentityServiceSecurityContext 属性返回 WindowsIdentity 类的实例并在使用该实例前调用 Impersonate 方法。

备注

请确保使用 Visual Basic Using 语句或 C# using 语句,以自动还原模拟操作。 如果不使用此语句,或者使用 Visual Basic 或 C# 以外的编程语言,请确保还原模拟级别。 否则可导致拒绝服务和特权升级攻击。

public class HelloService : IHelloService
{
    [OperationBehavior]
    public string Hello(string message)
    {
        WindowsIdentity callerWindowsIdentity =
        ServiceSecurityContext.Current.WindowsIdentity;
        if (callerWindowsIdentity == null)
        {
            throw new InvalidOperationException
           ("The caller cannot be mapped to a WindowsIdentity");
        }
        using (callerWindowsIdentity.Impersonate())
        {
            // Access a file as the caller.
        }
        return "Hello";
    }
}
Public Class HelloService
    Implements IHelloService

    <OperationBehavior()> _
    Public Function Hello(ByVal message As String) As String _
       Implements IHelloService.Hello
        Dim callerWindowsIdentity As WindowsIdentity = _
            ServiceSecurityContext.Current.WindowsIdentity
        If (callerWindowsIdentity Is Nothing) Then
            Throw New InvalidOperationException( _
              "The caller cannot be mapped to a WindowsIdentity")
        End If
        Dim cxt As WindowsImpersonationContext = callerWindowsIdentity.Impersonate()
        Using (cxt)
            ' Access a file as the caller.
        End Using

        Return "Hello"

    End Function
End Class

所有服务方法的模拟

在某些情况下,您必须在调用方上下文中执行服务的所有方法。 如果不想逐个方法地显式启用此功能,可以使用 ServiceAuthorizationBehavior。 如下面的代码所示,将 ImpersonateCallerForAllOperations 属性设置为 true。 即会从 ServiceAuthorizationBehavior 类的行为集合中检索 ServiceHost 。 另外请注意,应用于每个方法的 ImpersonationOperationBehaviorAttribute 属性也必须设置为 AllowedRequired

// Code to create a ServiceHost not shown.
ServiceAuthorizationBehavior MyServiceAuthorizationBehavior =
    serviceHost.Description.Behaviors.Find<ServiceAuthorizationBehavior>();
MyServiceAuthorizationBehavior.ImpersonateCallerForAllOperations = true;
' Code to create a ServiceHost not shown.
Dim MyServiceAuthorizationBehavior As ServiceAuthorizationBehavior
MyServiceAuthorizationBehavior = serviceHost.Description.Behaviors.Find _
(Of ServiceAuthorizationBehavior)()
MyServiceAuthorizationBehavior.ImpersonateCallerForAllOperations = True

下表说明 WCF 对于 ImpersonationOptionImpersonateCallerForAllServiceOperations 的所有可能组合的行为。

ImpersonationOption ImpersonateCallerForAllServiceOperations 行为
必需 不适用 WCF 模拟调用方
允许 false WCF 不模拟调用方
允许 WCF 模拟调用方
NotAllowed false WCF 不模拟调用方
NotAllowed 不允许。 (引发 InvalidOperationException 。)

从 Windows 凭据和缓存的令牌模拟获取的模拟级别

在某些情况下,当使用 Windows 凭据时,客户端能够部分控制服务执行的模拟级别。 情况之一是客户端指定 Anonymous 模拟级别。 另一种情况是执行缓存的令牌模拟。 这是通过设置 AllowedImpersonationLevel 类的 WindowsClientCredential 属性来完成的,此类可作为通用 ChannelFactory<TChannel> 类的属性进行访问。

备注

指定 Anonymous 模拟级别可导致客户端匿名登录到服务。 因此,无论是否执行模拟,服务都必须允许匿名登录。

客户端可以将模拟级别指定为 AnonymousIdentificationImpersonationDelegation。 只在指定的级别产生令牌,如下面的代码所示。

ChannelFactory<IEcho> cf = new ChannelFactory<IEcho>("EchoEndpoint");
cf.Credentials.Windows.AllowedImpersonationLevel  =
    System.Security.Principal.TokenImpersonationLevel.Impersonation;
Dim cf As ChannelFactory(Of IEcho) = New ChannelFactory(Of IEcho)("EchoEndpoint")
cf.Credentials.Windows.AllowedImpersonationLevel = _
System.Security.Principal.TokenImpersonationLevel.Impersonation

下表指定从缓存的令牌模拟时服务获取的模拟级别。

AllowedImpersonationLevel 服务具有 SeImpersonatePrivilege 服务和客户端能够委托 缓存的令牌 ImpersonationLevel
匿名 不适用 模拟
匿名 不适用 标识
标识 不适用 不适用 标识
模拟 不适用 模拟
模拟 不适用 标识
委托 委托
委托 模拟
委托 不适用 标识

从用户名凭据和缓存的令牌模拟获取的模拟级别

通过向服务传递客户端的用户名和密码,客户端可让 WCF 以该用户的身份登录,这等效于将 AllowedImpersonationLevel 属性设置为 Delegation。 (AllowedImpersonationLevelWindowsClientCredentialHttpDigestClientCredential 类中可用。)下表提供了当服务接收用户名凭据时获取的模拟级别。

AllowedImpersonationLevel 服务具有 SeImpersonatePrivilege 服务和客户端能够委托 缓存的令牌 ImpersonationLevel
不适用 委托
不适用 模拟
不适用 不适用 标识

从基于 S4U 的模拟获取的模拟级别

服务具有 SeTcbPrivilege 服务具有 SeImpersonatePrivilege 服务和客户端能够委托 缓存的令牌 ImpersonationLevel
不适用 模拟
不适用 标识
不适用 标识

将客户端证书映射到 Windows 帐户

客户端有可能使用证书向服务验证自己的身份,并让服务通过 Active Directory 将客户端映射到现有帐户。 下面的 XML 演示如何将服务配置为使用映射证书。

<behaviors>  
  <serviceBehaviors>  
    <behavior name="MapToWindowsAccount">  
      <serviceCredentials>  
        <clientCertificate>  
          <authentication mapClientCertificateToWindowsAccount="true" />  
        </clientCertificate>  
      </serviceCredentials>  
    </behavior>  
  </serviceBehaviors>  
</behaviors>  

下面的代码演示如何配置服务。

// Create a binding that sets a certificate as the client credential type.  
WSHttpBinding b = new WSHttpBinding();  
b.Security.Message.ClientCredentialType = MessageCredentialType.Certificate;  
  
// Create a service host that maps the certificate to a Windows account.  
Uri httpUri = new Uri("http://localhost/Calculator");  
ServiceHost sh = new ServiceHost(typeof(HelloService), httpUri);  
sh.Credentials.ClientCertificate.Authentication.MapClientCertificateToWindowsAccount = true;  

委托

若要委托给后端服务,服务必须使用客户端的 Windows 标识对后端服务执行 Kerberos 多段(不带 NTLM 回退的 SSPI)身份验证或 Kerberos 直接身份验证。 若要委托给后端服务,可以创建 ChannelFactory<TChannel> 和通道,然后在模拟客户端时通过该通道进行通信。 采用这种形式的委托时,后端服务与前端服务之间可以保持的距离取决于前端服务实现的模拟级别。 如果模拟级别为 Impersonation,则前端服务和后端服务必须在同一台计算机上运行。 如果模拟级别为 Delegation,则前端服务和后端服务既可以在不同的计算机上运行,也可以在同一台计算机上运行。 启用委托级别的模拟需要将 Windows 域策略配置为允许委托。 有关配置 Active Directory 以提供委托支持的详细信息,请参阅 Enabling Delegated Authentication(启用委托的身份验证)。

备注

如果客户端使用与后端服务上的 Windows 帐户相对应的用户名和密码向前端服务进行身份验证,则前端服务可通过重用客户端的用户名和密码,向后端服务进行身份验证。 这是一种功能特别强大的标识流形式,因为将用户名和密码传递到后端服务会使后端服务可以执行模拟,但它不会形成委托,因为未使用 Kerberos。 Active Directory 对委托的控制不适用于用户名和密码的身份验证。

作为模拟级别功能的委托功能

模拟级别 服务可以执行跨进程的委托 服务可以执行跨计算机的委托
Identification No
Impersonation No
Delegation

下面的代码示例演示如何使用委托。

public class HelloService : IHelloService
{
    [OperationBehavior(Impersonation = ImpersonationOption.Required)]
    public string Hello(string message)
    {
        WindowsIdentity callerWindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity;
        if (callerWindowsIdentity == null)
        {
            throw new InvalidOperationException
             ("The caller cannot be mapped to a Windows identity.");
        }
        using (callerWindowsIdentity.Impersonate())
        {
            EndpointAddress backendServiceAddress = new EndpointAddress("http://localhost:8000/ChannelApp");
            // Any binding that performs Windows authentication of the client can be used.
            ChannelFactory<IHelloService> channelFactory = new ChannelFactory<IHelloService>(new NetTcpBinding(), backendServiceAddress);
            IHelloService channel = channelFactory.CreateChannel();
            return channel.Hello(message);
        }
    }
}
Public Class HelloService
    Implements IHelloService

    <OperationBehavior(Impersonation:=ImpersonationOption.Required)> _
    Public Function Hello(ByVal message As String) As String Implements IHelloService.Hello
        Dim callerWindowsIdentity As WindowsIdentity = ServiceSecurityContext.Current.WindowsIdentity
        If (callerWindowsIdentity Is Nothing) Then
            Throw New InvalidOperationException("The caller cannot be mapped to a Windows identity.")
        End If

        Dim backendServiceAddress As EndpointAddress = New EndpointAddress("http://localhost:8000/ChannelApp")
        ' Any binding that performs Windows authentication of the client can be used.
        Dim channelFactory As ChannelFactory(Of IHelloService) = _
          New ChannelFactory(Of IHelloService)(New NetTcpBinding(), backendServiceAddress)
        Dim channel As IHelloService = channelFactory.CreateChannel()
        Return channel.Hello(message)
    End Function
End Class

如何将应用程序配置为使用受约束的委托

在使用受约束的委托之前,必须将发送方、接收方和域控制器配置为使用受约束的委托。 下面的过程列出启用受约束的委托的步骤。 有关委托和受约束委托之间的区别的详细信息,请参阅 Windows Server 2003 Kerberos Extensions (Windows Server 2003 Kerberos 扩展)中讨论受约束委托的部分。

  1. 在域控制器上,为用于运行客户端应用程序的帐户清除 “敏感帐户,不能被委派” 复选框。

  2. 在域控制器上,为用于运行客户端应用程序的帐户选中 “帐户可以委派其他帐户” 复选框。

  3. 在域控制器上,通过单击 “信任计算机作为委派” 选项,对中间层计算机进行配置,以便信任该计算机进行委托。

  4. 在域控制器上,通过单击 “仅信任此计算机来委派指定的服务” 选项,将中间层计算机配置为使用受约束的委托。

有关配置受约束委托的更多详细说明,请参阅 Kerberos 协议转换和受约束的委托

请参阅