Подтвердить что ты не робот

Отключить сертификат клиента 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, которые говорят с облачным сервисом сторонних разработчиков.

ИЛИ

  1. Пропустить SSL-авторизацию клиента для наших контроллеров webapi на передней панели?

Наш сервер web.config является сложным, но соответствующий фрагмент ниже:

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

И скриншот клиента, попадающего на обычную конечную точку WebAPI, но пытающуюся аутентификацию SSL клиента (происходит в любом браузере, Chrome, Firefox или IE) enter image description here

4b9b3361

Ответ 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, этот параметр может иметь смысл.

Надеюсь, что это поможет.