在*一些* WebAPI 控制器上禁用 SSL 客户端证书?

Posted

技术标签:

【中文标题】在*一些* WebAPI 控制器上禁用 SSL 客户端证书?【英文标题】:Disable SSL client certificate on *some* WebAPI controllers? 【发布时间】: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 证书。 (下图)

如果有办法

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

    为我们的前端驱动 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 不存在,因为客户端应用程序是浏览器 我认为这个问题更适合服务器故障。 【参考方案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 管道中编写自定义委托处理程序。您可以找到客户端证书委托处理程序 here 和 here。然后你可以让这个处理程序激活“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 的内容消息。

以下是您的选择:

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

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

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

希望这会有所帮助。

【讨论】:

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

以上是关于在*一些* WebAPI 控制器上禁用 SSL 客户端证书?的主要内容,如果未能解决你的问题,请参考以下文章

API Gateway w/WebAPI 端点在同一用户桥接网络上。 WebAPI 需要 SSL?

为啥在禁用 webdav 后,在本地工作的 WebApi 的 DELETE 请求上出现 405 方法不允许错误?

Service Fabric 多个 SSL 安全 WebAPI 和证书翻转

安全扫描扫到服务器的SSL存在隐患,建议禁用SSL,启用TLS,这个在WINDOWS和LINUX上,如何操作?

禁用SSL证书检查双绞线代理

使用 htaccess 在 home.php 上禁用 SSL 不起作用?