调试 Windows 身份验证错误

使用 Windows 身份验证作为安全机制时,安全支持提供程序接口(SSPI)处理安全进程。 当 SSPI 层发生安全错误时,它们由 Windows Communication Foundation (WCF) 显示。 本主题提供一个框架和一组问题来帮助诊断错误。

有关 Kerberos 协议的概述,请参阅 Kerberos 解释;有关 SSPI 的概述,请参阅 SSPI

对于 Windows 身份验证,WCF 通常使用 协商 安全支持提供程序 (SSP),该提供程序在客户端和服务之间执行 Kerberos 相互身份验证。 如果 Kerberos 协议不可用,则 WCF 默认回退到 NT LAN 管理器(NTLM)。 但是,可以将 WCF 配置为仅使用 Kerberos 协议(如果 Kerberos 不可用,则会引发异常)。 还可以将 WCF 配置为使用 Kerberos 协议的受限形式。

调试方法

基本方法如下所示:

  1. 确定是否使用 Windows 身份验证。 如果使用任何其他方案,则本主题不适用。

  2. 如果确定使用的是 Windows 身份验证,请确定您的 WCF 配置是使用 Kerberos 直接认证还是协商认证。

  3. 确定配置是使用 Kerberos 协议还是 NTLM 后,可以在正确的上下文中了解错误消息。

Kerberos 协议和 NTLM 的可用性

Kerberos SSP 要求域控制器充当 Kerberos 密钥分发中心(KDC)。 仅当客户端和服务都使用域标识时,Kerberos 协议才可用。 在其他帐户组合中,使用 NTLM,如下表所述。

表标头显示服务器可能使用的帐户类型。 左列显示客户端可能使用的帐户类型。

本地用户 本地系统 域用户 域计算机
本地用户 NTLM NTLM NTLM NTLM
本地系统 匿名 NTLM 匿名 NTLM 匿名 NTLM 匿名 NTLM
域用户 NTLM NTLM Kerberos Kerberos
域计算机 NTLM NTLM Kerberos Kerberos

具体而言,这四种帐户类型包括:

  • 本地用户:仅计算机用户配置文件。 例如:MachineName\AdministratorMachineName\ProfileName

  • 本地系统:未加入域的计算机上的内置帐户 SYSTEM。

  • 域用户:Windows 域中的用户帐户。 例如: DomainName\ProfileName

  • 域机器:一个拥有机器身份的进程,在加入 Windows 域的机器上运行。 例如: MachineName\Network Service

注释

当调用Open类的ServiceHost方法时,将捕获服务凭据。 每当客户端发送消息时,就会读取客户端凭据。

常见的 Windows 身份验证问题

本部分讨论一些常见的 Windows 身份验证问题和可能的补救措施。

Kerberos 协议

Kerberos 协议的 SPN/UPN 问题

在使用 Windows 身份验证并且 SSPI 使用或协商 Kerberos 协议时,客户端终结点使用的 URL 必须包括服务主机在服务 URL 中的完全限定域名。 这假设运行服务的帐户拥有访问计算机的默认服务主体名称(SPN)密钥的权限,该密钥是在将计算机加入到 Active Directory 域时创建的。通常,这通过在网络服务帐户下运行服务来实现。 如果服务无权访问计算机的 SPN 密钥,则您必须在客户端终结点标识中提供运行该服务的账户的正确 SPN 或用户主体名称(UPN)。 有关 WCF 如何与 SPN 和 UPN 配合使用的详细信息,请参阅 服务标识和身份验证

在负载均衡方案中,例如 Web 场或 Web 花园,一种常见做法是为每个应用程序定义唯一帐户,将 SPN 分配给该帐户,并确保应用程序的所有服务在该帐户中运行。

要获取服务账户的 SPN,你需要是 Active Directory 域的管理员。 有关详细信息,请参阅 适用于 Windows 的 Kerberos 技术补充

Kerberos 协议直接要求服务在域计算机帐户下运行

当属性 ClientCredentialType 设置为 WindowsNegotiateServiceCredential 属性设置为 false时,将发生这种情况,如以下代码所示。

WSHttpBinding b = new WSHttpBinding();
// By default, the WSHttpBinding uses Windows authentication
// and Message mode.
b.Security.Message.NegotiateServiceCredential = false;
Dim b As New WSHttpBinding()
' By default, the WSHttpBinding uses Windows authentication 
' and Message mode.
b.Security.Message.NegotiateServiceCredential = False

若要解决此问题,请在已加入域的计算机上使用域计算机帐户(如网络服务)运行服务。

委托要求凭据协商

若要与委托一起使用 Kerberos 身份验证协议,必须用凭据协商实现 Kerberos 协议(有时称作“多路”或“多步”Kerberos)。 如果不用凭据协商实现 Kerberos 协议(有时称作“单稳”或“单路”Kerberos),则会引发异常。

若要用凭据协商实现 Kerberos,请执行下列步骤:

  1. 通过将 AllowedImpersonationLevel 设置为 Delegation 来实现委派。

  2. 要求 SSPI 协商:

    1. 如果使用标准绑定,请将 NegotiateServiceCredential 属性设置为 true.

    2. 如果使用自定义绑定,请将AuthenticationModeSecurity元素的属性设置为 SspiNegotiated

  3. 通过禁用 NTLM 来要求 SSPI 协商使用 Kerberos:

    1. 在代码中执行此作,并使用以下语句: ChannelFactory.Credentials.Windows.AllowNtlm = false

    2. 或者,可以在配置文件中通过将 allowNtlm 属性设置为 false。 此属性包含在 <窗口中>

NTLM 协议

协商 SSP 回退到 NTLM,但 NTLM 已被禁用

属性 AllowNtlm 设置为 false,这会导致 Windows Communication Foundation (WCF) 在使用 NTLM 时尽力引发异常。 将此属性设置为 false 不能阻止通过网络发送 NTLM 凭据。

下面演示了如何禁用回退到 NTLM。

CalculatorClient cc = new
    CalculatorClient("WSHttpBinding_ICalculator");
cc.ClientCredentials.Windows.AllowNtlm = false;
Dim cc As New CalculatorClient("WSHttpBinding_ICalculator")
cc.ClientCredentials.Windows.AllowNtlm = False

NTLM 登录失败

客户端凭据在服务上无效。 检查是否已正确设置用户名和密码,并对应于运行服务的计算机已知的帐户。 NTLM 使用指定的凭据登录到服务的计算机。 虽然凭据在运行客户端的计算机上可能有效,但如果凭据在服务计算机上无效,则此登录将失败。

发生匿名 NTLM 登录,但默认情况下禁用匿名登录

创建客户端时,该 AllowedImpersonationLevel 属性将设置为 Anonymous以下示例中所示,但默认情况下服务器不允许匿名登录。 之所以发生这种情况,是因为类的属性AllowAnonymousLogons的默认值WindowsServiceCredentialfalse.

以下客户端代码尝试启用匿名登录(请注意默认属性为 Identification)。

CalculatorClient cc =
    new CalculatorClient("WSHttpBinding_ICalculator");
cc.ClientCredentials.Windows.AllowedImpersonationLevel =
System.Security.Principal.TokenImpersonationLevel.Anonymous;
Dim cc As New CalculatorClient("WSHttpBinding_ICalculator")
cc.ClientCredentials.Windows.AllowedImpersonationLevel = _
System.Security.Principal.TokenImpersonationLevel.Anonymous

以下服务代码将更改默认值,以启用服务器的匿名登录。

Uri httpUri = new Uri("http://localhost:8000/");
ServiceHost sh = new ServiceHost(typeof(Calculator), httpUri);
sh.Credentials.WindowsAuthentication.AllowAnonymousLogons = true;
Dim httpUri As New Uri("http://localhost:8000/")
Dim sh As New ServiceHost(GetType(Calculator), httpUri)
sh.Credentials.WindowsAuthentication.AllowAnonymousLogons = True

有关模拟的详细信息,请参阅 委派和模拟

或者,客户端使用内置帐户 SYSTEM 作为 Windows 服务运行。

其他问题

未正确设置客户端凭据

Windows 身份验证使用通过 WindowsClientCredential 类的 ClientCredentials 属性返回的 ClientBase<TChannel> 实例,而不是通过 UserNamePasswordClientCredential 返回的实例。 下面显示了一个不正确的示例。

CalculatorClient cc = new
    CalculatorClient("WSHttpBinding_ICalculator");
cc.ClientCredentials.UserName.UserName = GetUserName(); // wrong!
cc.ClientCredentials.UserName.Password = GetPassword(); // wrong!
Dim cc As New CalculatorClient("WSHttpBinding_ICalculator")
cc.ClientCredentials.UserName.UserName = GetUserName() ' wrong!
cc.ClientCredentials.UserName.Password = GetPassword() ' wrong!

下面显示了正确的示例。

CalculatorClient cc = new
    CalculatorClient("WSHttpBinding_ICalculator");
// This code returns the WindowsClientCredential type.
cc.ClientCredentials.Windows.ClientCredential.UserName = GetUserName();
cc.ClientCredentials.Windows.ClientCredential.Password = GetPassword();
Dim cc As New CalculatorClient("WSHttpBinding_ICalculator")
' This code returns the WindowsClientCredential type.            
cc.ClientCredentials.Windows.ClientCredential.UserName = GetUserName()
cc.ClientCredentials.Windows.ClientCredential.Password = GetPassword()

SSPI 不可用

以下作系统在用作服务器时不支持 Windows 身份验证:Windows XP 家庭版、Windows XP Media Center Edition 和 Windows Vista 家庭版。

用不同身份进行开发和部署

如果在一台计算机上开发应用程序,并部署在另一台计算机上,并使用不同的帐户类型在每台计算机上进行身份验证,则可能会遇到不同的行为。 例如,假设使用 SSPI Negotiated 身份验证模式在 Windows XP Pro 计算机上开发应用程序。 如果使用本地用户帐户进行身份验证,将使用 NTLM 协议。 开发应用程序后,将服务部署到 Windows Server 2003 计算机,该计算机在域帐户下运行。 此时,客户端将无法对服务进行身份验证,因为它将使用 Kerberos 和域控制器。

另请参阅