使用SSL证书时,底层连接被关闭:发送过程中发生了意外错误。

152

问题

我在日志中看到了以下异常信息:

The underlying connection was closed: An unexpected error occurred on a send.

这个错误会在任意时间(1-4小时)内突然出现,导致我们与电子邮件营销系统的 OEM 集成失败。

我的网站托管在 Windows Server 2008 R2 上,使用的是 IIS 7.5.7600。

该网站包含大量的 OEM 组件和全面的仪表盘。除了我们在仪表板中作为 iframe 解决方案使用的一个电子邮件营销组件外,其他所有元素都能正常工作。

它的工作方式是,我发送一个 HttpWebRequest 对象,其中包含所有凭据,然后我会得到一条 URL,我将其放入一个 iframe 中并进行操作。

但是,它只能运行一段时间(1-4小时),然后从

webRequest.GetResponse();

调用开始就出现上述异常。

即使系统试图从 httpWebRequest 获取 URL,也会出现同样的异常。

唯一使它再次工作的方法是:

  • 重新启动应用程序池
  • 在 web.config 中进行任何编辑。

我已经尝试了所有我能想到的选项,但都没有成功。

尝试的选项

明确添加了以下选项:

  • keep-alive = false

  • keep-alive = true

  • 增加了超时时间:

    <httpRuntime maxRequestLength="2097151" executionTimeout="9999999" enable="true" requestValidationMode="2.0" />

我已将此页面上传到一个非 SSL 网站上,以检查我们生产服务器上的 SSL 证书是否会导致连接有所下降。

非常感谢任何解决方案建议!

代码


    Public Function CreateHttpRequestJson(ByVal url) As String
        Try
            Dim result As String = String.Empty
            Dim httpWebRequest = DirectCast(WebRequest.Create("https://api.xxxxxxxxxxx.com/api/v3/externalsession.json"), HttpWebRequest)
            httpWebRequest.ContentType = "text/json"
            httpWebRequest.Method = "PUT"
            httpWebRequest.ContentType = "application/x-www-form-urlencoded"
            httpWebRequest.KeepAlive = False
            'ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3

            'TODO change the integratorID to the serviceproviders account Id, useremail 
            Using streamWriter = New StreamWriter(httpWebRequest.GetRequestStream())
                Dim json As String = New JavaScriptSerializer().Serialize(New With { _
                Key .Email = useremail, _
                Key .Chrome = "None", _
                Key .Url = url, _
                Key .IntegratorID = userIntegratorID, _
                Key .ClientID = clientIdGlobal _
                })

                'TODO move it to the web.config, Following API Key is holonis accounts API Key
                SetBasicAuthHeader(httpWebRequest, holonisApiKey, "")
                streamWriter.Write(json)
                streamWriter.Flush()
                streamWriter.Close()

                Dim httpResponse = DirectCast(httpWebRequest.GetResponse(), HttpWebResponse)
                Using streamReader = New StreamReader(httpResponse.GetResponseStream())
                    result = streamReader.ReadToEnd()
                    result = result.Split(New [Char]() {":"})(2)
                    result = "https:" & result.Substring(0, result.Length - 2)
                End Using
            End Using
            Me.midFrame.Attributes("src") = result
        Catch ex As Exception
            objLog.WriteLog("Error:" & ex.Message)
            If (ex.Message.ToString().Contains("Invalid Email")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Email Taken")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Invalid Access Level")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Unsafe Password")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Invalid Password")) Then
                'TODO Show message on UI
            ElseIf (ex.Message.ToString().Contains("Empty Person Name")) Then
                'TODO Show message on UI
            End If
        End Try
    End Function
  

    Public Sub SetBasicAuthHeader(ByVal request As WebRequest, ByVal userName As [String], ByVal userPassword As [String])
        Dim authInfo As String = Convert.ToString(userName) & ":" & Convert.ToString(userPassword)
        authInfo = Convert.ToBase64String(Encoding.[Default].GetBytes(authInfo))
        request.Headers("Authorization") = "Basic " & authInfo
    End Sub

你最终解决了这个问题吗? - Brett G
13
是的,我用这段代码成功地让它工作了。ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls Or SecurityProtocolType.Ssl3。 - Arvind Morwal
3
@user3458212 你应该将你的评论添加为一个答案。 - icc97
2
在我的情况下,使用Visual Studio 15运行网站一切都很顺利,但最终由于我无法升级服务器中的框架,并且强制使用TLS 1.2和禁用keep-alive不起作用,我不得不设置一个中间Web服务器来代理目标Web服务器,以避免连接中断。 - José Roberto García Chico
16个回答

251

对我来说,它是tls12。

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;

54
请注意,您必须小心,因为此更改对您的AppDomain全局有效,将导致对任何不提供TLS 1.2的站点的调用失败(如果要传输的数据确实很敏感,则您可能会更喜欢)。 如果想使用TLS 1.2但仍允许1.1和1.0,则需要将它们进行逻辑或运算:ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; - Dusty
一样的情况,你救了我的命,我花了很多时间才弄清楚RestSharp出了什么问题。 - darul75
1
该解决方案也适用于那些不使用RestSharp以及不使用ServicePointManager的人。只需在您的WebRequest调用或其他用于发出请求的地方复制并粘贴上述行即可。由于以上原因,我最初忽略了此解决方案。 - goku_da_master
5
或者只需将其添加到已有的代码中... System.Net.ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12; - uosjead
10
要在PowerShell中执行此操作,请将它们“二进制或”在一起,如下所示:[Net.ServicePointManager] ::SecurityProtocol =[Net.SecurityProtocolType] ::Tls12-bor [Net.SecurityProtocolType] ::Tls11-bor [Net.SecurityProtocolType] ::Tls - Adam S
对于VB.Net,请使用Xor:ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 Xor SecurityProtocolType.Tls11 Xor SecurityProtocolType.Tls - DavidScherer

86

5
太棒了!我只想补充一点,(SecurityProtocolType)768 可以用于“Tls11”(即 TLS 1.1)。 - Solomon Rutzky

31

前往您的web.config/App.config验证您正在使用哪个 .Net 运行时

  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
  </startup>

以下是解决方案:

  1. .NET 4.6及以上版本。您无需执行任何额外的工作即可支持TLS 1.2,因为它是默认支持的。

  2. .NET 4.5。虽然支持TLS 1.2,但它不是默认协议。您需要选择使用它。以下代码将使TLS 1.2成为默认设置,请确保在连接到安全资源之前执行它:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12

  1. .NET 4.0。不支持TLS 1.2,但如果系统上已安装.NET 4.5(或更高版本),则即使应用程序框架不支持它,您仍然可以选择使用TLS 1.2。唯一的问题是,.NET 4.0中的SecurityProtocolType没有TLS1.2的条目,因此我们必须使用此枚举值的数值表示:

ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

  1. .NET 3.5或更低版本。不支持TLS 1.2(*)也没有解决方法。请将应用程序升级到更高版本的框架。

3
在连接到安全资源之前,执行以下关键步骤:+1 - 确保在创建请求 var request = (HttpWebRequest)WebRequest.Create(url); 后设置 SecurityProtocol。如果你是先创建请求再设置 SecurityProtocol,那么第一个请求将会失败,但后续的请求会正常。将设置的操作放在创建请求对象之前即可解决问题。 - Lazlow

28

以下代码解决了问题

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls Or SecurityProtocolType.Ssl3

8
这个方案可行,但需要注意 ServicePointManager.SecurityProtocol 是一个静态对象,这意味着更改这个值将影响所有后续的 WebRequestWebClient 调用。如果您想让 ServicePointManager 拥有不同的设置,可以创建单独的 AppDomain。有关更多详细信息,请参阅 https://dev59.com/a2865IYBdhLWcg3wfemU。 - stack247
1
了解此代码的有用附加阅读:https://dev59.com/zV8d5IYBdhLWcg3w1FB0#26392698 - Jon Schneider
@Marnee 我把它放在我的应用程序组合根中,这样它就在任何 I/O 发生之前设置好了。 - JG in SD
@Marnee 把它放在静态构造函数中,这样它就只会在第一次访问类时被执行一次。尽管如此,我还是得启用所有协议以涵盖所有情况。 - Nyerguds
在创建 WebRequest 对象之前设置这个值。如果对象已经使用“旧”的 ServicePointManager 设置创建了,即使您在向服务器发出请求之前设置了新的设置,它也会使用那些旧的设置。 - Janeks Bergs
在2014年,SecurityProtocolType.Ssl3被POODLE攻击所破坏。 请勿使用SSLv3。https://en.wikipedia.org/wiki/POODLE - undefined

16

最近几天,我一直遇到同样的问题,这个集成也曾经“之前可以正常工作”。

由于极度沮丧,我刚刚尝试了

 ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Ssl3;

这对我很有帮助...即使整合严格只使用SSLv3。

我意识到有些事情不对,因为Fiddler报告说有一个"空的TLS协商密码"或类似的东西。

希望它能起作用!


请注意,即使您的代码不需要 TLS,如果它正在与需要 TLS 的服务器通信,则会尝试进行 TLS 协商,如果无法协商则会失败。空的 TLS 协商密码是期望您最终提供的 TLS 协议交换的插槽。之前可能“可以工作”,因为服务器管理员可能刚刚在您的应用程序正在通信的服务器上启用了 TLS。 - vapcguy
在2014年,SecurityProtocolType.Ssl3被POODLE攻击所破坏。请勿使用SSLv3。详细信息请参考https://en.wikipedia.org/wiki/POODLE。 - undefined

15
在我的情况下,我连接的网站已升级到TLS 1.2。因此,我必须在我的 Web 服务器上安装 .net 4.5.2 以支持它。

这个能在机器的注册表层面上完成吗?我已经禁用了所有的SSL协议,只留下了TLS 1.0、1.1、1.2,但是据我所知,为了符合PCI合规性,任何低于TLS 1.2的协议都应该尽快移除。 - brendo234

7

只需要添加以下代码:

ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;


2
请在您的答案中提供解释。 - Word Rearranger
4
这个回答和之前的回答一样没有增加任何新的内容。 - Arvindh Mani

6
我发现这是一个迹象,表明您部署代码的服务器安装了旧版本的.NET框架,不支持TLS 1.1或TLS 1.2。修复步骤如下:
  1. 在生产服务器(IIS和SQL)上安装最新的.NET运行时
  2. 在开发机器上安装最新的.NET开发人员包
  3. 将Visual Studio项目中的“目标框架”设置更改为最新的.NET框架。
您可以从此URL获取最新的.NET开发人员包和运行时: http://getdotnet.azurewebsites.net/target-dotnet-platforms.html

将目标框架从4.5.2更改为4.6.1,并开始工作,谢谢Patrick。 - Vivek Sharma

6

我发现更新以下代码的web.config文件可以解决该问题。

我的.Net框架版本设置为4.0,在组织层面升级TLS协议后,突然开始遇到此问题。因此,我参考了"Guo Huang"的答案并更新了targetFramework,问题得以解决。

<system.web>
        <compilation debug="true" targetFramework="4.7.2" />
        <httpRuntime targetFramework="4.7.2"  maxRequestLength="102400" executionTimeout="3600"/>
</system.web>

在我的情况下,相同的代码会随机出现异常。我尝试了很多解决方案,但只有这个有效 - executionTimeout="3600" 的部分。谢谢! - Wojciech X

5
我们遇到了一个问题,一个访问我们API的网站出现了“底层连接已关闭:在发送时发生意外错误”的消息。他们的代码混合了.NET 3.x和2.2,据我所知这意味着他们正在使用TLS 1.0。
下面的答案可以帮助您通过启用TLS 1.0、SSL 2和SSL3来诊断问题,但要非常清楚的是,长期以来,这三个协议都被视为不安全,不应再使用
为了使我们的IIS响应他们的API调用,我们不得不在IIS服务器上添加注册表设置,以明确启用TLS的版本 - 注意:在进行这些更改后,您必须重新启动Windows服务器(而不仅仅是IIS服务)。
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.0\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.0\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.1\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.1\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.2\Client] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS
1.2\Server] "DisabledByDefault"=dword:00000000 "Enabled"=dword:00000001

如果这样做不起作用,您还可以尝试添加SSL 2.0的条目进行实验。
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001

[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server]
"DisabledByDefault"=dword:00000000
"Enabled"=dword:00000001

要明确的是,这不是一个好的解决方案,正确的解决方案是让调用者使用TLS 1.2,但上述方法可以帮助诊断问题所在。
您可以使用此PowerShell脚本加快添加这些注册表项的速度:
$ProtocolList       = @("SSL 2.0","SSL 3.0","TLS 1.0", "TLS 1.1", "TLS 1.2")
$ProtocolSubKeyList = @("Client", "Server")
$DisabledByDefault = "DisabledByDefault"
$Enabled = "Enabled"
$registryPath = "HKLM:\\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\"

foreach($Protocol in $ProtocolList)
{
    Write-Host " In 1st For loop"
        foreach($key in $ProtocolSubKeyList)
        {         
            $currentRegPath = $registryPath + $Protocol + "\" + $key
            Write-Host " Current Registry Path $currentRegPath"
            if(!(Test-Path $currentRegPath))
            {
                Write-Host "creating the registry"
                    New-Item -Path $currentRegPath -Force | out-Null             
            }
            Write-Host "Adding protocol"
                New-ItemProperty -Path $currentRegPath -Name $DisabledByDefault -Value "0" -PropertyType DWORD -Force | Out-Null
                New-ItemProperty -Path $currentRegPath -Name $Enabled -Value "1" -PropertyType DWORD -Force | Out-Null    
    }
}
 
Exit 0

这是从微软为VMM设置TLS帮助页面修改过的脚本版本。basics.net文章最初启发我查看这些设置。


我们的问题涉及通过Team City进行发布管道,在更改了您的生产服务器证书时突然停止。我们已经将服务器更改为仅使用TLS1.2,但我们的Team City管道停止工作...按照指南添加注册表项并重新启动服务器...成功了!!!感谢tomRedox!! - Gwasshoppa
@Gwasshoppa,再次强调上面的方法只是为了诊断问题而采取的临时措施。现在你知道这是一个TLS版本的问题,解决方案是更改发布管道以便它可以使用TLS1.2,然后再次关闭TLS <1.2和SSL 2和3。我已经稍微更新了上面的答案以强调这一点。 - tomRedox
"SSL 3.0"在2014年遭受了POODLE攻击的破坏。请勿使用SSLv3 en.wikipedia.org/wiki/POODLE。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接