WCF 的委派和模拟

模拟 是一种常见技术,服务用于限制客户端对服务域资源的访问。 服务域资源可以是计算机资源,例如本地文件(模拟),也可以是另一台计算机上的资源,例如文件共享(委派)。 有关示例应用程序,请参阅 模拟客户端。 有关如何使用模拟的示例,请参阅 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 帐户)的 WSHttpBindingWSDualHttpBindingNetTcpBinding

  • 使用 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 方法之前模拟调用方。 任何在方法 Hello 中尝试访问本机资源的行为,只有当资源的访问控制列表(ACL)允许调用方的访问权限时才会成功。 若要启用模拟,请将 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 标识并以强制方式执行模拟。 为此,请使用 WindowsIdentity 属性的 ServiceSecurityContext 来返回 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。 另请注意,应用于每个方法的 Impersonation 属性必须设置为 OperationBehaviorAttributeAllowed

// 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

下表描述了所有可能的组合 ImpersonationOptionImpersonateCallerForAllServiceOperations 的 WCF 行为。

ImpersonationOption ImpersonateCallerForAllServiceOperations 行为
必选 n/a WCF 模拟调用方
允许 WCF 不模拟调用方
允许 WCF 模拟调用方
不允许 WCF 不模拟调用方
不允许 禁止。 (引发 InvalidOperationException 。)

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

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

注释

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

客户端可以将模拟级别指定为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
匿名 是的 n/a 模仿
匿名 n/a 标识
标识 n/a n/a 标识
模仿 是的 n/a 模仿
模仿 n/a 标识
代表团 是的 是的 代表团
代表团 是的 模仿
代表团 n/a 标识

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

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

AllowedImpersonationLevel 服务具有 SeImpersonatePrivilege 服务和客户端能够委托 缓存的令牌 ImpersonationLevel
n/a 是的 是的 代表团
n/a 是的 模仿
n/a n/a 标识

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

服务具有 SeTcbPrivilege 服务具有 SeImpersonatePrivilege 服务和客户端能够委托 缓存的令牌 ImpersonationLevel
是的 是的 n/a 模仿
是的 n/a 标识
n/a n/a 标识

将客户端证书映射到 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 以支持委派的详细信息,请参阅 “启用委派身份验证”。

注释

当客户端使用与后端服务上的 Windows 帐户对应的用户名和密码向前端服务进行身份验证时,前端服务可以通过重用客户端的用户名和密码向后端服务进行身份验证。 这是一种特别强大的标识流形式,因为将用户名和密码传递给后端服务使后端服务能够执行模拟,但它不构成委派,因为不使用 Kerberos。 委派上的 Active Directory 控件不适用于用户名和密码身份验证。

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

模拟级别 服务可以执行跨进程的委托 服务可以执行跨计算机的委托
Identification
Impersonation 是的
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 扩展 中讨论受限委派的部分。

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

  2. 在域控制器上,为运行客户端应用程序的帐户选中 “帐户被信任用于委托” 复选框。

  3. 在域控制器上,通过单击“信任计算机进行委派”选项,配置中层计算机,使其可以进行委派。

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

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

另请参阅