没有凭据协商的 Windows 客户端的消息安全

下面的方案演示了由 Kerberos 协议保护的 Windows Communication Foundation (WCF) 客户端和服务。

服务和客户端位于相同的域或可信域。

注意

此方案与 Windows 客户端的消息安全性之间的区别在于,此方案在发送应用程序消息前并不与服务协商服务凭据。 此外,由于这需要 Kerberos 协议,所以此方案需要一个 Windows 域环境。

没有凭证协商的消息安全

特征 说明
安全模式 消息
互操作性 是,WS-Security 使用 Kerberos 令牌配置文件兼容的客户端
身份验证(服务器) 服务器和客户端的相互身份验证
身份验证(客户端) 服务器和客户端的相互身份验证
完整性
机密性
Transport HTTP
绑定 WSHttpBinding

服务

下面的代码和配置应独立运行。 执行下列操作之一:

  • 使用代码(而不使用配置)创建独立服务。

  • 使用提供的配置创建服务,但不定义任何终结点。

代码

下面的代码创建使用消息安全的服务终结点。 代码禁用了服务凭据协商并禁止建立安全上下文令牌 (SCT)。

备注

若要使用没有协商的 Windows 凭据类型,则服务的用户帐户必须能够访问在 Active Directory 域注册的服务主体名称 (SPN)。 可通过两种方式实现此目的:

  1. 使用 NetworkServiceLocalSystem 帐户运行服务。 由于这些帐户能够访问在计算机连接 Active Directory 域时建立的计算机 SPN,因此 WCF 将自动在服务的元数据(Web 服务描述语言 (WSDL))中的该服务终结点内生成正确的 SPN 元素。

  2. 使用任意 Active Directory 域帐户运行服务。 在这种情况下,您需要为该域帐户建立一个 SPN。 执行此操作的一种方法是使用 Setspn.exe 实用工具。 为该服务的帐户创建 SPN 后,配置 WCF 以通过服务的元数据 (WSDL) 将该 SPN 发布到服务的客户端。 通过为公开的终结点设置终结点标识(或通过应用程序配置文件或代码)也可完成此操作。 下面的示例以编程方式发布标识。

有关 SPN、Kerberos 协议和 Active Directory 的详细信息,请参阅针对 Windows 的 Kerberos 技术补充信息。 若要详细了解终结点标识,请参阅 SecurityBindingElement 身份验证模式

// Create the service host.
ServiceHost myServiceHost = new ServiceHost(typeof(Calculator));

// Create the binding.
WSHttpBinding binding = new WSHttpBinding();
binding.Security.Mode = SecurityMode.Message;
binding.Security.Message.ClientCredentialType =
     MessageCredentialType.Windows;

// Disable credential negotiation and establishment of the
// security context.
binding.Security.Message.NegotiateServiceCredential = false;
binding.Security.Message.EstablishSecurityContext = false;

// Create a URI for the endpoint address.
Uri httpUri = new Uri("http://localhost/Calculator");

// Create the EndpointAddress with the SPN for the Identity.
EndpointAddress ea = new EndpointAddress(httpUri,
    EndpointIdentity.CreateSpnIdentity("service_spn_name"));

// Get the contract from the ICalculator interface (not shown here).
// See the sample applications for an example of the ICalculator.
ContractDescription contract = ContractDescription.GetContract(
    typeof(ICalculator));

// Create a new ServiceEndpoint.
ServiceEndpoint se = new ServiceEndpoint(contract, binding, ea);

// Add the service endpoint to the service.
myServiceHost.Description.Endpoints.Add(se);

// Open the service.
myServiceHost.Open();
Console.WriteLine("Listening...");
Console.ReadLine();

// Close the service.
myServiceHost.Close();
' Create the service host.
Dim myServiceHost As New ServiceHost(GetType(ServiceModel.Calculator))

' Create the binding.
Dim binding As New WSHttpBinding()
binding.Security.Mode = SecurityMode.Message
binding.Security.Message.ClientCredentialType = _
   MessageCredentialType.Windows

' Disable credential negotiation and establishment of the
' security context.
binding.Security.Message.NegotiateServiceCredential = False
binding.Security.Message.EstablishSecurityContext = False

' Create a URI for the endpoint address.
Dim httpUri As New Uri("http://localhost/Calculator")

' Create the EndpointAddress with the SPN for the Identity.
Dim ea As New EndpointAddress(httpUri, _
EndpointIdentity.CreateSpnIdentity("service_spn_name"))

' Get the contract from the ICalculator interface (not shown here).
' See the sample applications for an example of the ICalculator.
Dim contract As ContractDescription = ContractDescription.GetContract(GetType(ICalculator))

' Create a new ServiceEndpoint.
Dim se As New ServiceEndpoint(contract, binding, ea)

' Add the service endpoint to the service.
myServiceHost.Description.Endpoints.Add(se)

' Open the service.
myServiceHost.Open()
Console.WriteLine("Listening...")
Console.ReadLine()

' Close the service.
myServiceHost.Close()

配置

以下配置可代替代码使用。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <behaviors />
    <services>
      <service behaviorConfiguration="" name="ServiceModel.Calculator">
        <endpoint address="http://localhost/Calculator"
                  binding="wsHttpBinding"
                  bindingConfiguration="KerberosBinding"
                  name="WSHttpBinding_ICalculator"
                  contract="ServiceModel.ICalculator"
                  listenUri="net.tcp://localhost/metadata" >
         <identity>
            <servicePrincipalName value="service_spn_name" />
         </identity>
        </endpoint>
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <binding name="KerberosBinding">
          <security>
            <message negotiateServiceCredential="false"
                     establishSecurityContext="false" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client />
  </system.serviceModel>
</configuration>

客户端

下面的代码和配置应独立运行。 执行下列操作之一:

  • 使用代码(和客户端代码)创建独立客户端。

  • 创建不定义任何终结点地址的客户端。 而使用将配置名称作为自变量的客户端构造函数。 例如:

    CalculatorClient cc = new CalculatorClient("EndpointConfigurationName");
    
    Dim cc As New CalculatorClient("EndpointConfigurationName")
    

代码

下面的代码将配置客户端。 安全模式设置为 Message,客户端凭据类型设置为 Windows。 请注意,NegotiateServiceCredentialEstablishSecurityContext 属性设置为 false

注意

若要使用没有协商的 Windows 凭据类型,则必须在开始与服务进行通信前使用服务的帐户 SPN 配置客户端。 客户端使用 SPN 获取 Kerberos 令牌对与服务的通信进行身份验证和保护。 下面的示例演示如何使用服务的 SPN 配置客户端。 如果使用 ServiceModel 元数据实用工具 (Svcutil.exe) 生成客户端,则服务的 SPN 将自动从服务的元数据 (WSDL) 传播到客户端(如果服务的元数据包含该信息)。 关于如何将服务配置为在服务的元数据中包含服务的 SPN 的更多信息,请参见本主题后面的“服务”部分。

有关 SPN、Kerberos 和 Active Directory 的更多信息,请参阅面向 Windows 的 Kerberos 技术补充。 若要详细了解终结点标识,请参阅 SecurityBindingElement 身份验证模式主题。

// Create the binding.
WSHttpBinding myBinding = new WSHttpBinding();
myBinding.Security.Mode = SecurityMode.Message;
myBinding.Security.Message.ClientCredentialType =
    MessageCredentialType.Windows;

// Disable credential negotiation and the establishment of
// a security context.
myBinding.Security.Message.NegotiateServiceCredential = false;
myBinding.Security.Message.EstablishSecurityContext = false;

// Create the endpoint address and set the SPN identity.
// The SPN must match the identity of the service's SPN.
// If using SvcUtil to generate a configuration file, the SPN
// will be published as the <servicePrincipalName> element under the
// <identity> element.
EndpointAddress ea = new EndpointAddress(
new Uri("http://machineName/Calculator"),
EndpointIdentity.CreateSpnIdentity("service_spn_name"));

// Create the client.
CalculatorClient cc =
    new CalculatorClient(myBinding, ea);

// Begin using the client.

try
{
    cc.Open();
    Console.WriteLine(cc.Add(200, 1111));
    Console.ReadLine();

    // Close the client.
    cc.Close();
}
' Create the binding.
Dim myBinding As New WSHttpBinding()
myBinding.Security.Mode = SecurityMode.Message
myBinding.Security.Message.ClientCredentialType = _
   MessageCredentialType.Windows

' Disable credential negotiation and the establishment of
' a security context.
myBinding.Security.Message.NegotiateServiceCredential = False
myBinding.Security.Message.EstablishSecurityContext = False

' Create the endpoint address and set the SPN identity.
' The SPN must match the identity of the service's SPN.
' If using SvcUtil to generate a configuration file, the SPN
' will be published as the <servicePrincipalName> element under the
' <identity> element.
Dim ea As New EndpointAddress(New Uri("http://machineName/calculator"), _
EndpointIdentity.CreateSpnIdentity("service_spn_name"))

' Create the client.
Dim cc As New CalculatorClient(myBinding, ea)

' Begin using the client.
Try
    cc.Open()

    Console.WriteLine(cc.Add(100, 11))
    Console.ReadLine()

    ' Close the client.
    cc.Close()
Catch tex As TimeoutException
    Console.WriteLine(tex.Message)
    cc.Abort()
Catch cex As CommunicationException
    Console.WriteLine(cex.Message)
    cc.Abort()
Finally
    Console.WriteLine("Closed the client")
    Console.ReadLine()
End Try

配置

下面的代码将配置客户端。 请注意,必须将 <servicePrincipalName> 元素设置为与在 Active Directory 域中为服务帐户注册的服务 SPN 相匹配。

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="WSHttpBinding_ICalculator" >
          <security mode="Message">
            <message clientCredentialType="Windows"
                     negotiateServiceCredential="false"
                     establishSecurityContext="false" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost/Calculator"
                binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_ICalculator"
                contract="ICalculator"
                name="WSHttpBinding_ICalculator">
        <identity>
          <servicePrincipalName value="service_spn_name" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

请参阅