【问题标题】:.NET https requests with different security protocols across threads跨线程使用不同安全协议的 .NET https 请求
【发布时间】:2014-11-27 09:36:50
【问题描述】:

我维护一个相当复杂的 ASP.NET 应用程序(一个定制的 NopCommerce 3.10)。 不同场景下需要通过HTTPS连接第三方服务器。我正在通过HttpWebRequest 类执行此操作。

其中一些服务器配置不当:

其中一个第三方服务器(例如 Server A)需要 SSL3 协议类型,如果设置了另一种协议类型,连接就会失败。 如果使用 SSL3 执行连接,另一台服务器(例如 Server B)提供了不正确的证书。更准确地说,它提供了一个带有错误 CN(通用名称)的证书。但是,如果我从一开始就使用 TLS,那么证书就可以了。

我使用ServicePointManager.ServerCertificateValidationCallback 回调检查 SSL 策略错误,确定了上述问题。

更改安全协议是通过ServicePointManager.SecurityProtocol 完成的,这是一个静态属性。但是,客户端对我的应用程序执行的触发上述 HTTPS 连接的请求可能碰巧在不同的线程中并行运行。

如果我,例如:将安全协议设置为所需的类型,执行HTTPS请求,然后将其设置回Server A,我无法保证同时请求是否需要连接到 服务器 B 不会将 ServicePointManager.SecurityProtocol 更改为 服务器 A 所需的值以外的值。 我相信这是一个典型的静态变量多线程问题。

根据我的研究,我确定 .NET 没有为每个 WebRequest 实例提供使用特定 SSL 协议的方法。

我正在考虑以下解决方案:

  • 在我的应用程序中对所有传出 HTTPS 连接进行排队,以确保每个连接都使用正确的 SSL 协议
  • 为每个 HTTPS 请求构建一个单独的应用域(https://stackoverflow.com/a/3107692/1288522 建议)
  • 将 HTTPS 请求更改为低级 TCP 连接,并为每个连接强制执行不同的 SSL 协议
  • 制作一个代理 asp.net 应用程序,它将对传出请求进行排队

注意:排队不会对性能造成巨大影响,因为所有客户端请求中只有一小部分实际上到达了相关代码。

但是,考虑到应用架构或粗略的解决方法(第三种解决方案),上述解决方案需要进行困难的重构

我的问题与to this one on msdn 非常相似,但是没有得到满意的答案。

是否有更直接或更有效的方法来确保每个 https 请求使用特定的 SSL 协议?

【问题讨论】:

  • 你有没有弄明白这件事?我们似乎在使用 TLS 1.0 协议向我们自己的 RESTful API 发送 HTTP 请求时遇到了类似的问题
  • @TomMiller 我从来没有参与实现每个请求使用不同安全协议的解决方案,因为所有与 SSL3 相关的代码都因“贵宾犬”漏洞而被删除或屏蔽。有点巧合的是,这个漏洞是在我提问两周后公开披露的。我当时考虑的可能解决方案列表将是一个起点。

标签: c# asp.net multithreading ssl


【解决方案1】:

我们遇到了同样的问题,并采用了您提到的应用程序域方法,根据此处提出的内容实施了一个解决方案,这是一篇关于如何在单独的应用程序域中管理代码执行的非常好的文章:

http://www.superstarcoders.com/blogs/posts/executing-code-in-a-separate-application-domain-using-c-sharp.aspx

我们几乎照原样使用他的隔离类:

  public sealed class Isolated<T> : IDisposable where T : MarshalByRefObject
  {
    private AppDomain _domain;
    private readonly T _value;

    public Isolated()
    {
        _domain = AppDomain.CreateDomain("Isolated:" + Guid.NewGuid(), null, AppDomain.CurrentDomain.SetupInformation);

        var type = typeof(T);

        _value = (T)_domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
    }

    public T Value
    {
        get
        {
            return _value;
        }
    }

    public void Dispose()
    {
        if (_domain == null) return;

        AppDomain.Unload(_domain);

        _domain = null;
    }
}

然后我们有一个围绕标准 WebClient 的包装器,它允许设置协议:

public class WebClient : MarshalByRefObject, IWebClient
{
    public WebClientResponse GetResponse(string address)
    {
        return GetResponse(address, null);
    }

    public WebClientResponse GetResponse(string address, string securityProtocol)
    {
        if (!string.IsNullOrWhiteSpace(securityProtocol))
            ServicePointManager.SecurityProtocol = (SecurityProtocolType)Enum.Parse(typeof(SecurityProtocolType), securityProtocol);

        var response = new WebClientResponse();

        try
        {
            using (var wc = new System.Net.WebClient())
            {
              // <do stuff>
            }
        }
        catch (Exception ex)
        {
            response.Exception = new GetResponseException(string.Format("Unable to get response from {0}", address), ex);
        }

        return response;
    }
}

[Serializable]
public class WebClientResponse
{
    public Exception Exception { get; set; }

    public string Response { get; set; }
}

 [Serializable]
public class GetResponseException : Exception
{
    public GetResponseException(string message, Exception innerException)
        : base(message, innerException)
    {
    }

    public GetResponseException(SerializationInfo info, StreamingContext context)  : base(info, context)
    {
    }
}

将它们捆绑在一起,我们有代码可以确定它是否需要覆盖当前设置的协议。如果是,它会启动隔离的应用程序域,如果不是,它使用现有的 WebClient:

...
        WebClientResponse webClientResponse;

        if (!string.IsNullOrWhiteSpace(ForceSecurityProtocol))
        {
            using (var isolated = new Isolated<WebClient>())
            {
                webClientResponse = isolated.Value.GetResponse(url, ForceSecurityProtocol);
            }
        }
        else
        {
            webClientResponse = _webClient.GetResponse(url);
        }
...

请注意,我们的应用并没有在应用程序的高吞吐量区域中使用,因此无论我们为使用这种方法付出的性能代价是什么,都不会产生任何影响。如果我们要把这样的东西放在一个阻碍我们网络应用程序大量流量的地方,我们会做一些测试。

【讨论】:

  • 抱歉,这是一个愚蠢的问题 - 我在哪里可以找到 IWebClient 的定义?
  • @NathanaelSchulte 它只是 WebClient 类上公共方法的接口,因此使用此 WebClient 的代码可以独立测试。
【解决方案2】:

您可以简单地使用您提到的回调 (ServicePointManager.ServerCertificateValidationCallback) 来实现自定义验证逻辑:只有当错误来自该恶意服务器时,您才能绕过错误。

【讨论】:

    猜你喜欢
    • 2017-03-08
    • 1970-01-01
    • 1970-01-01
    • 2021-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多