WinForms 应用程序使用 NTLM 身份验证连接到 WCF 服务时遇到问题。
<LongStory-可选>
原始应用程序是我在 2005 年编写的,作为我的第一个 C# 代码。通过小的修改,它运行良好。Web 服务是带有数据集的 asmx。最近有时间升级它,因为:性能问题和客户端上的 Windows 10 升级(Crystal Reports 10.5.37 需要升级到 x64 并且 SAP 站点上没有官方运行时下载)。我使用 WCF、.NET 4.8 重写了它,将 Crystal 替换为库以创建 Excel 文件。在开发人员环境中,新版本运行良好,除了 500 代码。没有考虑 Rest 和 .NET Core(太难解释了;不是技术原因)。
当用户启动新版本时,第一个直接问题是“访问被拒绝”。然后它以管理员身份启动。错误代码:“应用程序无法启动,因为它的并排配置不正确”。然后我试图在事件查看器中获取更多信息但没有成功:没有信息。sxstrace 工具引发访问被拒绝。几天后我明白了:我在包含国家非英语字符的配置文件中做了评论。删除评论后,连接期间出现错误 500。
<\LongStory-可选>
应用程序调用仅返回常量值 1 的验证服务。
错误消息:“响应消息的内容类型 text/html 与绑定的内容类型不匹配 (text/xml; charset=utf-8)。
如果使用自定义编码器,请确保正确实现 IsContentTypeSupported 方法。响应的前 75 个字节是:
该页面无法显示,因为发生了内部服务器错误。” . 未使用 Https。
奇怪的是,在 IE 中打开 .svc 一次后,错误消失了,直到工作站重新启动。这证明客户端和服务器工作正常。同时旧应用程序没有这样的问题。因此,我尝试使用“Web 参考”而不是“服务参考”。看来“网络参考”工作正常。接下来我在客户端尝试了不同的设置。我发现“服务参考”在此更改后有效:client.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
使用提琴手工具,我发现消息交换几乎相同,除了最终结果:更改之前我有状态 401/401/500,更改 401/401/200 之后,其他差异是时间和 NTLM base64 值,仅此而已。
我认为这不是解决方案,而只是一种解决方法。您知道如何使用 WCF 正确处理 NTLM 身份验证吗?
我的代码和配置的基本部分:
var client = new ServiceClient();
client.ClientCredentials.Windows.ClientCredential.UserName = Dialog1.textBox1.Text;
client.ClientCredentials.Windows.ClientCredential.Domain = Dialog1.textBox2.Text;
client.ClientCredentials.Windows.ClientCredential.Password = Dialog1.textBox3.Text;
try
{
i = client.Verify().Status;
} ...
配置:
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicEndpoint" sendTimeout="infinite" >
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://wro67zt2/AlsbWebServices/WindService/WindService.svc" binding="basicHttpBinding" bindingConfiguration="basicEndpoint" contract="WindService.IService" name="basicEndpoint"/>
</client>
</system.serviceModel>
我认为服务器端配置是不必要的。它适用于 Web 参考,因此我可以假设这里一切正常。我试过“Windows”而不是“Ntlm”,但问题仍然存在,它在请求/响应消息中将字符串 NTLM 更改为协商,并且消息数量从 3 变为 2(401/500 或 401/200)。
编辑 - 服务器配置:
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="aspnet:UseTaskFriendlySynchronizationContext" value="true" />
</appSettings>
<system.web>
<compilation debug="true" targetFramework="4.7.2" />
<httpRuntime targetFramework="4.7.2"/>
<globalization culture="en-GB" requestEncoding="utf-8" responseEncoding="utf-8"/>
<pages controlRenderingCompatibilityVersion="4.0" clientIDMode="AutoID"/>
<identity impersonate="false" />
</system.web>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="basicBinding" >
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" realm="XXXXX"/>
<message clientCredentialType="UserName" algorithmSuite="Default"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service behaviorConfiguration="WindService.Behavior" name="WindLibrary.Service">
<endpoint
address=""
binding="basicHttpBinding"
bindingConfiguration="basicBinding"
name="basicEndpoint"
bindingNamespace="http://XXXXX.com/services/prime/"
contract="WindLibrary.IService">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="WindService.Behavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<protocolMapping>
<add scheme="http" binding="basicHttpBinding" bindingConfiguration="HttpBinding" />
</protocolMapping>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
<directoryBrowse enabled="false"/>
</system.webServer>
</configuration>