【发布时间】:2017-05-25 10:19:42
【问题描述】:
我正在尝试编写 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 安全通道”
我能够毫无问题地执行来自 Postman 的两个请求,无需任何特殊修改。
这使我找到了调查此错误的路径,并且我发现了许多有用的 SO 帖子讨论该主题,包括:
The request was aborted: Could not create SSL/TLS secure channel
Could not create SSL/TLS secure channel, despite setting ServerCertificateValidationCallback
这些帮助我指出了正确的方向,即查看将 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 requests with different security protocols across threads
How to use SSL3 instead of TLS in a particular HttpWebRequest?
但这对我来说似乎过于复杂/老套。处理创建应用程序域真的是唯一的解决方案吗?或者这是我根本不应该尝试解决的问题,而是与相关服务的所有者一起解决?我很好奇它可以在一个环境/服务器上使用 TLS,但不能在另一个环境/服务器上使用。
编辑 我做了一些更多的玩这个。我更改了我的客户端以使用这篇博文中很好地概述的方法(使用不同的应用程序域来隔离更改 ServicePointManager.SecurityProtocol 的代码):
https://bitlush.com/blog/executing-code-in-a-separate-application-domain-using-c-sharp
这实际上效果很好,可以作为备用解决方案。但我还了解到,相关服务的提供者有一个不同的端点(相同的 URL,不同的端口),它使用 TLS 1.2 进行保护。幸运的是,通过在 global.asax.cs 应用程序启动事件中扩展我的 SecurityProtocol 设置:
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
我能够在所有环境中与服务正常通信。它也不会影响我与其他服务(例如 CyberSource)的现有集成。
但是 - 现在有一个新的但相关的问题。 为什么这个调用只有在我像上面那样扩展 SecurityProtocolType 时才有效?我的其他集成,比如 CyberSource,不需要这个。然而这个确实如此。从我在浏览器中看到的内容来看,它们似乎都使用 TLS 1.2 进行了保护。
【问题讨论】: