Отключить SSL-сертификат клиента на * некоторых * контроллерах WebAPI?
Редактировать для будущих читателей: к сожалению, награда за вознаграждение не работает; Я ничего не могу с этим поделать. Но прочитайте мой собственный ответ ниже (через тестирование) - подтвердил работу с минимальными изменениями кода
У нас есть облачная служба Azure (WebRole), которая полностью находится в ASP.NET WebAPI 2.2 (нет MVC, внешний интерфейс - Angular). Некоторые из наших контроллеров / конечных точек REST взаимодействуют с сторонней облачной службой по протоколу SSL (аутентификация клиента / взаимная аутентификация клиента), а остальные контроллеры / конечные точки взаимодействуют с интерфейсом HTML5/AngularJS, также через SSL (но более традиционная аутентификация сервера). SSL). У нас нет конечной точки без SSL. Мы включили Client SSL через задачу запуска облачной службы, например:
IF NOT DEFINED APPCMD SET APPCMD=%SystemRoot%\system32\inetsrv\AppCmd.exe
%APPCMD% unlock config /section:system.webServer/security/access
Проблема: этот параметр распространяется на весь сайт, поэтому даже когда пользователи переходят на первую страницу (например, https://domain.com/, возвращает index.html для angularJS), их браузер запрашивает сертификат SSL-сертификата клиента. (изображение ниже)
Если есть способ либо
- Ограничить клиентские запросы SSL-сертификатов только контроллерами WebAPI, которые общаются со сторонней облачной службой?
ИЛИ ЖЕ
- Пропустить аутентификацию клиента SSL для наших внешних контроллеров Webapi?
Файл web.config нашего сервера сложный, но соответствующий фрагмент приведен ниже:
<system.webServer>
<security>
<access sslFlags="SslNegotiateCert" />
</security>
</system.webServer>
И снимок экрана с клиентом, который подключается к обычной конечной точке WebAPI, но пытается выполнить SSL-аутентификацию клиента (происходит в любом браузере, Chrome, Firefox или IE)
3 ответа
К сожалению, ответ Клефтериса, который получил награду, не работает. Он пытается работать слишком поздно в конвейере / обработке HTTP-сервера, чтобы получить сертификат клиента, но этот пост дал мне некоторые идеи.
Решение основано на 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;
}
}
Вы можете просто разрешить простой http-трафик на уровне web.config и написать для этого собственный обработчик делегирования в конвейере 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
}
}
Обратите внимание, что в случае, если вам нужны делегаты для каждого маршрута, вы не должны помещать их в глобальный список обработчиков сообщений.
К сожалению, это не может быть настроено на уровне контроллера. Сервер решает, какой контроллер использовать, основываясь на содержимом HTTP-запроса (обычно это путь запроса). SSL защищает содержимое HTTP-сообщения путем его шифрования, а путь запроса является частью зашифрованного сообщения. Канал SSL должен быть настроен до отправки каких-либо сообщений HTTP, поэтому конфигурация канала SSL (например, пытается ли сервер согласовать сертификат клиента или нет) не может зависеть от содержимого любого HTTP Сообщения.
Итак, вот ваши варианты:
Раскрутите вторую веб-роль, настроенную так, чтобы не согласовывать клиентские сертификаты. Для этого вам понадобится второй домен, так как по сути это отдельный сервис. Таким образом, у вас будет https://domain.com/ указывающий на сертификат, не являющийся клиентским сертификатом, и https://foo.domain.com/ указывающий на тот, который требует клиентских сертификатов.
Используйте ту же веб-роль, но настройте второй порт для прослушивания IIS и настройте его так, чтобы он не согласовывал сертификат клиента. Однако использование нестандартных портов является проблемой, поскольку один из ваших клиентов должен будет выполнить https://domain.com:444/ (или какой-либо другой порт, кроме 443).
Отключите согласование сертификата клиента по всем направлениям. Это может не работать в зависимости от того, как ваша служба обращается к клиентскому сертификату, но обычно, когда вы обращаетесь к свойству ClientCertificate в объекте System.Web.HttpRequest (или эквивалентном), он будет согласовывать сертификат по требованию. Это означает, что он прозрачно разрывает существующий сеанс SSL и устанавливает новый, на этот раз бросая вызов клиенту для сертификата. Это довольно неэффективно, поскольку настройка SSL-соединения в первую очередь занимает несколько циклов, а делать это дважды болезненно. Но в зависимости от доступных параметров, требований к производительности для запросов, использующих клиентские сертификаты, и от того, будете ли вы многократно использовать соединение из keep-alives, этот вариант может иметь смысл.
Надеюсь это поможет.