【问题标题】:Disable SSL client certificate on *some* WebAPI controllers?在*一些* WebAPI 控制器上禁用 SSL 客户端证书?
【发布时间】:2015-02-04 02:16:36
【问题描述】:

为未来的读者编辑:不幸的是,赏金授予了答案 不工作;我现在对此无能为力。但请阅读我自己的答案 下面(通过测试) - 确认可以使用最少的代码更改

我们有一个完全在 ASP.NET WebAPI 2.2 中的 Azure 云服务 (WebRole)(没有 MVC,前端是 Angular)。我们的一些控制器/REST 端点通过 SSL(客户端证书身份验证/相互身份验证)与第 3 方云服务通信,其余控制器/端点与 HTML5/AngularJS 前端通信,也通过 SSL(但更传统的服务器身份验证SSL)。我们没有任何非 SSL 端点。我们通过云服务启动任务启用了客户端 SSL,例如:

IF NOT DEFINED APPCMD SET APPCMD=%SystemRoot%\system32\inetsrv\AppCmd.exe
%APPCMD% unlock config /section:system.webServer/security/access

问题:该设置是站点范围的,因此即使用户点击第一页(比如https://domain.com,返回 angularJS 的 index.html),他们的浏览器也会要求他们提供客户端 SSL 证书。 (下图)

如果有办法

  1. 将客户端 SSL 证书请求限制为仅与第 3 方云服务通信的 WebAPI 控制器?

  1. 为我们的前端驱动 webapi 控制器跳过客户端 SSL 身份验证?

我们服务器的web.config比较复杂,但是相关的sn-p如下:

<system.webServer>
  <security>
    <access sslFlags="SslNegotiateCert" />
  </security>
</system.webServer>

客户端访问常规 WebAPI 端点但尝试客户端 SSL 身份验证的屏幕截图(发生在任何浏览器、Chrome、Firefox 或 IE 中)

【问题讨论】:

  • 你能显示你的结果 serverWebConfig 和应用程序 WebConfig 吗?
  • 服务器 web.config 上面,应用程序 webconfig 不存在,因为客户端应用程序是浏览器
  • 我认为这个问题更适合服务器故障。

标签: c# ssl asp.net-web-api https client-certificates


【解决方案1】:

不幸的是,cleftheris 获得赏金的答案不起作用。它试图在 HTTP 服务器管道/处理中工作太晚以获取客户端证书,但this post 给了我一些想法。

该解决方案基于 web.config,它要求对“目录”进行特殊处理(也适用于虚拟文件夹或 WebAPI 路由)。

这是所需的逻辑:

https://www.server.com/acmeapi/** => 带有客户端证书的 SSL

https://www.server.com/** => SSL

这里是对应的配置

<configuration>
  ...
  <system.webServer>
    <!-- This is for the rest of the site -->
    <security>
      <access sslFlags="Ssl" />
    </security>
  </system.webServer>

  <!--This is for the 3rd party API endpoint-->
  <location path="acmeapi">
    <system.webServer>
      <security>
        <access sslFlags="SslNegotiateCert"/>
      </security>
    </system.webServer>
  </location>
...
</configuration>

奖励积分

以上将相应地设置 SSL 握手。现在,您仍然需要检查代码中的客户端 SSL 证书是否符合您的预期。这样做如下

控制器代码:

[RoutePrefix("acmeapi")]
[SslClientCertActionFilter] // <== key part!
public class AcmeProviderController : ApiController
{
    [HttpGet]
    [Route("{userId}")]
    public async Task<OutputDto> GetInfo(Guid userId)
    {
        // do work ...
    }
}

执行 SSL 客户端验证的实际属性如下。可用于装饰整个控制器或只是特定方法。

public class SslClientCertActionFilterAttribute : ActionFilterAttribute
{
    public List<string> AllowedThumbprints = new List<string>()
    {
        // Replace with the thumbprints the 3rd party
        // server will be presenting. You can make checks
        // more elaborate but always have thumbprint checking ...
        "0011223344556677889900112233445566778899",
        "1122334455667788990011223344556677889900" 
    };

    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        var request = actionContext.Request;

        if (!AuthorizeRequest(request))
        {
            throw new HttpResponseException(HttpStatusCode.Forbidden);
        }
    }

    private bool AuthorizeRequest(HttpRequestMessage request)
    {
        if (request==null)
            throw new ArgumentNullException("request");

        var clientCertificate = request.GetClientCertificate();

        if (clientCertificate == null || AllowedThumbprints == null || AllowedThumbprints.Count < 1)
        {
            return false;
        }

        foreach (var thumbprint in AllowedThumbprints)
        {
            if (clientCertificate.Thumbprint != null && clientCertificate.Thumbprint.Equals(thumbprint, StringComparison.InvariantCultureIgnoreCase))
            {
                return true;
            }
        }
        return false;
    }
}

【讨论】:

  • 这就像一个魅力。我找不到任何其他推荐可行解决方案的参考资料。谢谢
【解决方案2】:

您可以简单地在 web.config 级别允许纯 http 流量,并为此在 Web Api 管道中编写自定义委托处理程序。您可以找到客户端证书委托处理程序 herehere。然后你可以让这个处理程序激活“Per-Route”,就像在这个例子中找到的here

这就是您的路由配置的样子。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Routes.MapHttpRoute(
            name: "Route1",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );

        config.Routes.MapHttpRoute(
            name: "Route2",
            routeTemplate: "api2/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional },
            constraints: null,
            handler: new CustomCertificateMessageHandler()  // per-route message handler
        );

        config.MessageHandlers.Add(new SomeOtherMessageHandler());  // global message handler
    }
}

请注意,如果您需要“按路由”委派处理程序,则必须不要将它们放在全局消息处理程序列表中。

【讨论】:

  • 请注意,这是我回答中选项 #3 的实现,因此存在建立 SSL 会话两次的性能问题。
  • @DeepSpace101 看看这个codeplex workitem。列出的代码应该可以正常工作。
  • 致未来的读者:请阅读我对自己问题的回答。它已确认可以工作,并且需要最少的代码更改
【解决方案3】:

很遗憾,这无法在控制器级别进行配置。服务器根据 HTTP 请求的内容(通常是请求路径)决定使用哪个控制器。 SSL 通过加密来保护 HTTP 消息的内容,请求路径是加密消息的一部分。 SSL 通道需要在发送任何 HTTP 消息之前建立,这就是为什么 SSL 通道的配置(例如,服务器是否尝试协商客户端证书)不能依赖任何 HTTP 的内容消息。

以下是您的选择:

  1. 启动第二个 Web 角色,该角色配置为不协商客户端证书。为此,您需要第二个域,因为它本质上是一项单独的服务。因此,https://domain.com 指向非客户端证书,https://foo.domain.com 指向需要客户端证书的证书。

  2. 使用相同的 Web 角色,但设置第二个端口供 IIS 侦听,并将该端口配置为不协商客户端证书。不过,使用非标准端口很痛苦,因为您的一个客户将不得不使用https://domain.com:444(或除 443 之外的其他端口)。

  3. 全面禁用客户端证书协商。这可能不起作用,具体取决于您的服务如何访问客户端证书,但通常当您访问 System.Web.HttpRequest 对象(或等效对象)上的 ClientCertificate 属性时,它会按需协商证书。这意味着它透明地拆除现有的 SSL 会话并建立一个新的会话,这一次挑战客户端的证书。这是非常低效的,因为首先建立一个 SSL 连接需要多次往返,而且这样做两次很痛苦。但根据您的可用选项、使用客户端证书的请求的性能要求,以及您是否会从保持活动状态中获得大量连接重用,此选项可能有意义。

希望这会有所帮助。

【讨论】:

  • 如果不是控制器,我可以告诉 IIS 本身在哪些路径上强制(或跳过)客户端 SSL 吗?根据您的描述,它是做出该决策的正确层。
  • 不幸的是,你不能。您唯一可以使用的是 IP 和端口。当一个 HTTP 请求进来时,SSL 协商需要在服务器甚至找到路径之前发生。选项 #3 是您可以根据路径/控制器做出不同决策的最接近的方法。但效率较低。
  • @DeepSpace101 您能否尝试使用自己的 web.config 创建一个空的物理文件夹,然后在该路径下创建您的安全控制器路由?然后,您可以使用 Appcmd Unlock Config 命令仅解锁该位置的访问元素。
猜你喜欢
  • 2016-08-07
  • 2013-08-04
  • 2010-10-16
  • 2017-04-28
  • 2012-02-15
  • 2017-05-18
  • 2015-10-20
  • 2017-06-01
  • 2012-10-18
相关资源
最近更新 更多