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

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

我们有一个完全在ASP.NET WebAPI 2.2中的Azure云服务(WebRole)(没有MVC,前端是Angular)。 我们的一些控制器/ REST端点通过SSL(客户端证书身份validation/相互身份validation)与第三方云服务通信,其余控制器/端点与HTML5 / AngularJS前端通信,也通过SSL(但更传统的服务器身份validation) 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 ,返回angular.html的index.html),他们的浏览器也要求他们提供客户端SSL证书。 (下图)

如果有办法要么

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

要么

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

我们的服务器的web.config很复杂,但相关的代码片段如下:

      

并且客户端的屏幕截图访问常规WebAPI端点但尚未尝试客户端SSL身份validation(在任何浏览器,Chrome,Firefox或IE中都会发生) 在此处输入图像描述

不幸的是,cleftheris给予奖励的答案不起作用。 它试图在HTTP服务器管道/处理中工作太晚以获得客户端证书,但这篇文章给了我一些想法。

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

这是所需的逻辑:

https://www.server.com/acmeapi/ ** => SSL与客户证书

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

这是相应的配置

  ...               ...  

奖励积分

以上将相应地设置SSL握手。 现在,您仍然需要在代码中检查客户端SSL证书(如果它是您期望的那个)。 这样做如下

控制器代码:

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

上面执行SSL客户端validation的实际属性如下。 可以用来装饰整个控制器或只是特定的方法。

 public class SslClientCertActionFilterAttribute : ActionFilterAttribute { public List AllowedThumbprints = new List() { // 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; } } 

您可以在web.config级别上简单地允许纯HTTP流量,并在Web Api管道中为此编写自定义委托处理程序。 您可以在此处和此处找到客户端证书委派处理程序。 然后,您可以使此处理程序处于活动状态“Per-Route”,如下例所示:

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

 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 } } 

请注意,如果您需要“per-route”委派处理程序,则不得将它们放在全局消息处理程序列表中。

不幸的是,这不能在控制器级别配置。 服务器根据HTTP请求的内容(通常是请求路径)决定使用哪个控制器。 SSL通过加密来保护HTTP消息的内容,并且请求路径是加密消息的一部分。 需要在发送任何HTTP消息之前设置SSL通道,这就是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连接需要多次往返,并且做两次是痛苦的。 但是,根据您的可用选项,使用客户端证书的请求的性能要求,以及您是否会从保持活动中获得大量连接重用,此选项可能有意义。

希望这可以帮助。