我正在尝试编写C#代码,该代码针对用于计算Web应用程序中的销售税的REST服务端点发出Web请求.这是第三方服务,使用SSL进行保护.有两种环境,UAT和生产.运行webrequest的代码如下所示:
... var req = WebRequest.Create(url) as HttpWebRequest; req.Method = "POST"; req.ContentType = "application/json"; ... using (var webresponse = req.GetResponse()) { using (var responseStream = new StreamReader(webresponse.GetResponseStream())) { var respJson = responseStream.ReadToEnd(); calcResult = BuildResponse(calcRequest, respJson, consoleWriteRawReqResponse); } } return calcResult;
这对UAT环境很好.但是当我在生产环境中运行相同的代码时,我收到错误:
"无法创建SSL/TLS安全通道"
我很能没有问题,从邮差执行这两个请求,没有任何特殊的修改.
这导致我走上了调查这个错误的道路,我找到了很多有用的SO帖子来讨论这个话题,包括:
请求已中止:无法创建SSL/TLS安全通道
尽管设置了ServerCertificateValidationCallback,但无法创建SSL/TLS安全通道
这些帮助指向了我正确的方向,即查看将ServicePointManager.SecurityProtocol设置设置为不同的值,并使用ServicePointManager.ServerCertificateValidationCallback来调查错误.
我玩这些后发现的是以下内容:
UAT环境调用将使用Ssl3 |的默认设置 Tls(.NET 4.5.2的默认值),而生产环境则不会.
只有当我将此设置显式设置为Ssl3时,生产调用才会起作用.
该代码如下所示:
... var req = WebRequest.Create(url) as HttpWebRequest; req.Method = "POST"; req.ContentType = "application/json"; ... ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CertValidationCallback); using (var webresponse = req.GetResponse()) { using (var responseStream = new StreamReader(webresponse.GetResponseStream())) { var respJson = responseStream.ReadToEnd(); calcResult = BuildResponse(calcRequest, respJson, consoleWriteRawReqResponse); } } ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; return calcResult;
这尤其令人困惑,因为在查看Web浏览器中的端点时,我可以看到它们都受到相同通配符证书的保护,并且都使用TLS 1.0.
所以我希望将ServicePointManager.SecurityProtocol设置为TLS可以正常工作,但事实并非如此.
我真的想避免将ServicePointManager.SecurityProtocol显式设置为SSL3,因为我们的应用程序是一个Web应用程序,并且有多个其他通过SSL进行通信的集成点.这些都很好,我不想对它们的功能产生负面影响.即使我在调用之前设置了此设置,然后立即将其更改回来,我也存在遇到并发问题的风险,因为ServicePointManager.SecurityProtocol是静态的.
我也调查了这个话题,并不喜欢我读的内容.有提及使用不同的应用程序域:
.NET https跨线程使用不同安全协议的请求
如何在特定的HttpWebRequest中使用SSL3而不是TLS?
但这对我来说似乎过于复杂/苛刻.处理创建应用程序域真的是唯一的解决方案吗?或者这是我不应该试图解决的问题,而是与相关服务的所有者一起讨论?我非常好奇它可以在一个环境/服务器上使用TLS,但不能在另一个环境/服务器上使用.
编辑 我做了更多的玩这个.我更改了我的客户端以使用此博客文章中概述的方法(使用不同的应用程序域来隔离更改ServicePointManager.SecurityProtocol的代码):
https://bitlush.com/blog/executing-code-in-a-separate-application-domain-using-c-sharp
这实际上运作得很好,可能是后退解决方案.但我也了解到有问题的服务提供商有一个使用TLS 1.2保护的不同端点(相同的URL,不同的端口).值得庆幸的是,通过在global.asax.cs应用程序启动事件中扩展我的SecurityProtocol设置:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
我能够在所有环境中与服务进行良好的沟通.它也不会影响我现有的与其他服务的集成(例如,CyberSource).
但是 - 现在有一个新的但相关的问题.如果我如上所述扩展SecurityProtocolType,为什么这个调用才有效?我的其他集成,如CyberSource,并不需要这样.但是这个确实如此.并且它们似乎都是使用我在浏览器中看到的TLS 1.2来保护的.