Отключить сертификат клиента SSL на * некоторых * контроллерах WebAPI?
Изменить для будущих читателей: К сожалению, награда за награду не работает; теперь я ничего не могу с этим поделать. Но прочитайте мой собственный ответ ниже (через тестирование) - подтверждено для работы с минимальными изменениями кода
У нас есть служба облачного облака (WebRole), которая полностью находится в ASP.NET WebAPI 2.2 (без MVC, front end - Angular). Некоторые из наших контроллеров/конечных точек REST разговаривают с сторонним облачным сервисом через SSL (клиентский сертификат auth/взаимный auth), а остальные контроллеры/конечные точки разговаривают с интерфейсом HTML5/AngularJS, также через SSL (но более традиционный серверный auth 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. (изображение ниже)
Если есть способ либо
- Ограничить клиентские запросы SSL-сертификатов только контроллерам WebAPI, которые говорят с облачным сервисом сторонних разработчиков.
ИЛИ
- Пропустить SSL-авторизацию клиента для наших контроллеров webapi на передней панели?
Наш сервер web.config является сложным, но соответствующий фрагмент ниже:
<system.webServer>
<security>
<access sslFlags="SslNegotiateCert" />
</security>
</system.webServer>
И скриншот клиента, попадающего на обычную конечную точку WebAPI, но пытающуюся аутентификацию SSL клиента (происходит в любом браузере, Chrome, Firefox или IE)
![enter image description here]()
Ответы
Ответ 1
К сожалению, ответ cleftheris, который присудил награду, не работает. Он пытается слишком поздно работать с конвейером/обработкой 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;
}
}
Ответ 2
Вы можете просто разрешить простой 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
}
}
Обратите внимание, что если вам нужны обработчики делегирования "на маршрут", вы должны не поместить их в список обработчиков глобальных сообщений.
Ответ 3
К сожалению, это невозможно настроить на уровне контроллера. Сервер решает, какой контроллер использовать на основе содержимого 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, этот параметр может иметь смысл.
Надеюсь, что это поможет.