在分析一个WCF项目的过程中,遇到了一个问题,问题产生的根源在于下面的代码:
EndpointAddress endPoint = new EndpointAddress(new Uri(string.Format(Constants.LocoServerBaseAddress, address) + address));
抛出的错误为:A call to SSPI failed, see inner exception.然后当我查看inner exception的时候,看到了错误信息为:The target principal name is incorrect。
开始看上去,被搞得一头雾水,根本不知道该如何去处理,不过还好,在网上看到了一篇外国文章(WCF on intranet with windows authentication: Kerberos or NTLM (Part 1) ),看完以后,按照我的理解给翻译了出来,以备后用。呵呵,我总是这么的顺手牵羊啊。
还是让我先来restore一下这个exception 发生的情形吧:
首先,我将WCF的服务器端拷贝到另外一台机器上,这台机器和我本机不在同一个网段,均处于内网中,但是通过外网相连接。
然后开启服务端,服务端总是开启一会儿就挂掉,客户端连接就会出现如上的错误。我打开系统的Event Viewer,看到了一个错误:
The kerberos client received a KRB_AP_ERR_MODIFIED error from the server host/apaccmhkap038.apac.nsroot.net. This indicates that the password used to encrypt the kerberos service ticket is different than that on the target server. Commonly, this is due to identically named machine accounts in the target realm (APAC.NSROOT.NET), and the client realm. Please contact your system administrator.
很明显啊,客户端和服务端连接的时候,采用了kerberos验证方式。但是由于验证未通过,所以抛出了错误。
这里我们肯定想问,什么是kerberos?是用来干什么的?
其实,它属于windows认证机制中的方式之一,当一台机器中的WCF程序访问另外一台机器中的资源的时候,就会发生认证申请,kerberos 和 NTLM就是这两种认证方式。二者的具体定义请参见(http://msdn.microsoft.com/en-us/library/aa480609.aspx)和(http://msdn.microsoft.com/en-us/library/aa378749(v=vs.85).aspx),
下面的图示展示了什么情况下采用哪种认证方式:
Local User |
Local System |
Domain User |
Domain Machine |
|
Local User |
NTLM |
NTLM |
NTLM |
NTLM |
Local System |
Anonymous NTLM |
Anonymous NTLM |
Anonymous NTLM |
Anonymous NTLM |
Domain User |
NTLM |
NTLM |
Kerberos |
Kerberos |
Domain Machine |
NTLM |
NTLM |
Kerberos |
Kerberos |
如果WCF服务端和客户端都在本机,那么采用的是NTLM认证方式,所以不会出现错误,这也是为啥我在本机同时开启的时候,能够正常运行的原因。
但是如果WCF服务端和客户端分布在不同的Domain,那么二者连接时候,认证方式则变成了kerberos。所以刚才当我将服务端放入外网的时候,在系统日志发现了当认证不匹配时kerberos错误。
在上面的代码中,我做了一点修改,加上了如下的参数:
EndpointAddress endPoint = new EndpointAddress(new Uri(string.Format(Constants.LocoServerBaseAddress, address) + address), EndpointIdentity.CreateSpnIdentity("MySystem/Service1"));
加上以后,居然连接成功了。
这里不论我给CreateSpnIdentity任何值,程序都能够正确的运行。
所以,通过以上内容,我们的猜想如下:如果处在不同domain的服务端和客户端相互连接时,系统会首先使用机器名作为Uri,比如说,当我们连接net.tcp://remotemachine1:port/MyService的时候,WCF的客户端将会使用机器名称(EndpointIdentity.CreateSpnIdentity("remotemachine1"))作为认证的SPN,去调用service方法。当发现SPN存在但是不正确时,就转而使用NTLM认证,所以Client和Server就能连接了。当发现SPN存在并且正确时,就采用kerberos认证。当发现SPN不存在时,就直接采用kerberos认证,这也是错误出现的原因。
解决方法就是,在创建EndpointAddress的时候,加上
EndpointIdentity.CreateSpnIdentity("MySPNIdentity ")
或者在配置文件中:
<endpoint name="winservicenettcp" binding="netTcpBinding" bindingConfiguration="netTcp" address="net.tcp://myserver:12345/WcfPerfTest" contract="Contract.IPerfTest" behaviorConfiguration="WcfTestBehavior"> <identity> <servicePrincipalName value="MySPNIdentity "/> </identity> </endpoint>
其中字串可以为任何值,这样就能使用NTLM认证,从而避免错误出现。
需要注意的是,认证发生是Client端连接Server端的时候。