CORS, предполетные опции, IIS и WebApi2 - PullRequest
1 голос
/ 06 июня 2019

Перестал работать вызов извлечения SPA для конечной точки WebApi2 на другом сервере. Эти двое были друзьями в течение многих лет, но я недавно опубликовал оба конца, так что все было немного двусмысленно.

В этом случае Edge более полезен, чем Chrome, и он говорит мне, что я получаю 405 в ответ на запрос OPTIONS. Порывшись в SO и сети в целом, напомнил мне о настройке CORS в SPA, который ведет Kestrel, и просмотр исходного кода показал, что я уже сделал это. Просмотр заголовков запроса показывает требуемые заголовки с ожидаемыми значениями. Это не было неожиданностью, поскольку получение ответа 405 на запрос OPTIONS означает, что SPA отправляет один, что означает, что он настроен соответствующим образом.

Более подробно выяснилось, что я должен добавить пакет NuGet Microsoft.AspNet.WebApi.Cors в проект WebApi. Проверка показала, что она уже есть.

Просмотр WebApi в web.config показал это

<handlers>
  <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
  <remove name="OPTIONSVerbHandler" />
  <remove name="TRACEVerbHandler" />
  <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>

Я считал, что целью пакета CORS в конце WebApi является обработка глагола OPTIONS. Но в ответе 405 говорится, что этого не происходит, поэтому я прокомментировал <remove name="OPTIONSVerbHandler" /> и вот! они снова друзья.

Проблема решена? На самом деле, нет. Смысл обработки этого в приложении - сделать его менее зависимым от внешних параметров конфигурации. Что приводит меня к вопросу:

Что вы должны сделать, кроме добавления Microsoft.AspNet.WebApi.Cors с NuGet?

Это сервис WebApi2, который отлично работает, когда CORS не попадает между старыми друзьями. Он работает на .NET Framework 4.7.2 и размещается на IIS на Server 2012 с текущими пакетами обновлений. Как клиентское, так и серверное программное обеспечение работают в частной сети, поэтому CORS усложняет решение проблемы, которой у нас нет. Знание, как заставить его замолчать и перестать помогать, очень полезно.

1 Ответ

1 голос
/ 06 июня 2019

Я нашел ответ на свой вопрос в статье MSDN Брока Аллена .

В соответствии с политикой SO в отношении ответов только для ссылок часть статьи, которая отвечает на этот вопрос,Воспроизведено ниже против возможности разрыва ссылки.Хочу подчеркнуть, что нижеприведенная статья не является моей работой и полностью относится к Броку Аллену из Thinktecture.


Поддержка CORS в веб-API 2

Поддержка CORS в веб-APIполная структура, позволяющая приложению определять разрешения для запросов CORS.Фреймворк вращается вокруг концепции политики, которая позволяет вам указывать функции CORS, которые должны быть разрешены для любого данного запроса в приложении.

Во-первых, чтобы получить структуру CORS, вы должны обратиться к библиотекам CORS изваше приложение веб-API (по умолчанию на него не ссылаются никакие шаблоны веб-API в Visual Studio 2013).Среда Web API CORS доступна через NuGet в виде пакета Microsoft.AspNet.WebApi.Cors.Если вы не используете NuGet, он также доступен как часть Visual Studio 2013, и вам нужно сослаться на две сборки: System.Web.Http.Cors.dll и System.Web.Cors.dll (на моем компьютере эторасположены в C: \ Program Files (x86) \ Microsoft ASP.NET \ ASP.NET Web Stack 5 \ Packages).

Далее, чтобы выразить политику, Web API предоставляет собственный класс атрибутов, называемый EnableCorsAttribute.Этот класс содержит свойства для разрешенных источников, методов HTTP, заголовков запросов, заголовков ответов и разрешенных учетных данных (которые моделируют все детали спецификации CORS, обсужденной ранее).

Наконец, для WebПлатформа API CORS для обработки запросов CORS и выдачи соответствующих заголовков ответов CORS должна проверять каждый запрос в приложении.Веб-API имеет точку расширяемости для такого перехвата через обработчики сообщений.Соответственно, среда CORS Web API реализует обработчик сообщений с именем CorsMessageHandler.Для запросов CORS он будет обращаться к политике, выраженной в атрибуте вызываемого метода, и выдавать соответствующие заголовки ответа CORS.

EnableCorsAttribute Класс EnableCorsAttribute определяет, как приложение может выражать свой CORS.политика.Класс EnableCorsAttribute имеет перегруженный конструктор, который может принимать три или четыре параметра.Параметры (по порядку):

  1. Список разрешенных источников
  2. Список разрешенных заголовков запросов
  3. Список разрешенных методов HTTP
  4. Списокзаголовки ответа разрешены (необязательно)

Существует также свойство для разрешения учетных данных (SupportsCredentials) и другое свойство для указания значения продолжительности кэша предварительной проверки (PreflightMaxAge).

Рисунок 2 показывает пример применения атрибута EnableCors к отдельным методам на контроллере.Значения, используемые для различных параметров политики CORS, должны соответствовать запросам и ответам CORS, которые были показаны в предыдущих примерах.

Рисунок 2 Применение атрибута EnableCors к методам действий

public class ResourcesController : ApiController
{
  [EnableCors("http://localhost:55912", // Origin
              null,                     // Request headers
              "GET",                    // HTTP methods
              "bar",                    // Response headers
              SupportsCredentials=true  // Allow credentials
  )]
  public HttpResponseMessage Get(int id)
  {
    var resp = Request.CreateResponse(HttpStatusCode.NoContent);
    resp.Headers.Add("bar", "a bar value");
    return resp;
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "PUT",                          // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  [EnableCors("http://localhost:55912",       // Origin
              "Accept, Origin, Content-Type", // Request headers
              "POST",                         // HTTP methods
              PreflightMaxAge=600             // Preflight cache duration
  )]
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

Примечаниекаждый из параметров конструктора является строкой.Несколько значений указываются указанием списка через запятую (как указано для разрешенных заголовков запросов в Рисунок 2 ).Если вы хотите разрешить все источники, заголовки запросов или методы HTTP, вы можете использовать «*» в качестве значения (вы все равно должны быть явно указаны для заголовков ответов).

В additiПрименив атрибут EnableCors на уровне метода, вы также можете применить его на уровне класса или глобально к приложению. Уровень применения атрибута настраивает CORS для всех запросов на этом уровне и ниже в вашем коде Web API. Так, например, если применяется на уровне метода, политика будет применяться только к запросам для этого действия, тогда как при применении на уровне класса политика будет применяться ко всем запросам к этому контроллеру. Наконец, если применяется глобально, политика будет действовать для всех запросов.

Ниже приведен еще один пример применения атрибута на уровне класса. Параметры, используемые в этом примере, являются довольно разрешающими, поскольку для разрешенных источников, заголовков запросов и методов HTTP используются подстановочные знаки:

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
}

Если существует политика в нескольких местах, используется атрибут «ближайший», а остальные игнорируются (поэтому приоритет имеет метод, затем класс, а затем глобальный). Если вы применили политику на более высоком уровне, но затем хотите исключить запрос на более низком уровне, вы можете использовать другой класс атрибута с именем DisableCorsAttribute. Этот атрибут, по сути, является политикой без разрешенных разрешений.

Если у вас есть другие методы на контроллере, где вы не хотите разрешать CORS, вы можете использовать один из двух вариантов. Во-первых, вы можете быть явным в списке методов HTTP, как показано на Рисунок 3 . Или вы можете оставить подстановочный знак, но исключить метод Delete с атрибутом DisableCors, как показано на Рисунок 4 .

Рисунок 3 Использование явных значений для методов HTTP

[EnableCors("*", "*", "PUT, POST")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because DELETE is not in the method list above
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

Рисунок 4 Использование атрибута DisableCors

[EnableCors("*", "*", "*")]
public class ResourcesController : ApiController
{
  public HttpResponseMessage Put(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  public HttpResponseMessage Post(Resource data)
  {
    return Request.CreateResponse(HttpStatusCode.OK, data);
  }
  // CORS not allowed because of the [DisableCors] attribute
  [DisableCors]
  public HttpResponseMessage Delete(int id)
  {
    return Request.CreateResponse(HttpStatusCode.NoContent);
  }
}

CorsMessageHandler CorsMessageHandler должен быть включен для платформы CORS, чтобы он выполнял свою работу по перехвату запросов для оценки политики CORS и генерации заголовков ответа CORS. Включение обработчика сообщений обычно выполняется в классе конфигурации веб-API приложения, вызывая метод расширения EnableCors:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
  }
}

Если вы хотите предоставить глобальную политику CORS, вы можете передать экземпляр класса EnableCorsAttribute в качестве параметра методу EnableCors. Например, следующий код настроит глобально разрешающую политику CORS в приложении:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
  }
}

Как и в любом обработчике сообщений, CorsMessageHandler может быть альтернативно зарегистрирован по маршруту, а не глобально.

Вот и все для базовой «готовой» среды CORS в ASP.NET Web API 2. Одна приятная вещь в этой среде - это то, что она расширяема для более динамичных сценариев, о которых я расскажу позже.

Политика настройки

Из предыдущих примеров должно быть очевидно, что список источников (если подстановочный знак не используется) представляет собой статический список, скомпилированный в код веб-API. Хотя это может работать во время разработки или для определенных сценариев, этого недостаточно, если список источников (или другие разрешения) нужно определять динамически (скажем, из базы данных).

К счастью, инфраструктура CORS в веб-API расширяема, так что поддержка динамического списка источников проста. На самом деле структура настолько гибкая, что существует два основных подхода к настройке генерации политики.

Атрибут пользовательской политики CORS Один из подходов к включению динамической политики CORS состоит в разработке класса пользовательских атрибутов, который может генерировать политику из некоторого источника данных. Этот класс пользовательских атрибутов можно использовать вместо класса EnableCorsAttribute, предоставляемого веб-API. Этот подход прост и сохраняет детальное ощущение возможности применять атрибут к определенным классам и методам (а не применять его к другим) по мере необходимости.

Для реализации этого приложенияПроще говоря, вы просто создаете пользовательский атрибут, похожий на существующий класс EnableCorsAttribute.Основное внимание уделяется интерфейсу ICorsPolicyProvider, который отвечает за создание экземпляра CorsPolicy для любого данного запроса.На рисунке 5 приведен пример.

Рисунок 5 Пользовательский атрибут политики CORS

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method,
                AllowMultiple = false)]
public class EnableCorsForPaidCustomersAttribute :
  Attribute, ICorsPolicyProvider
{
  public async Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    if (await IsOriginFromAPaidCustomer(originRequested))
    {
      // Grant CORS request
      var policy = new CorsPolicy
      {
        AllowAnyHeader = true,
        AllowAnyMethod = true,
      };
      policy.Origins.Add(originRequested);
      return policy;
    }
    else
    {
      // Reject CORS request
      return null;
    }
  }
  private async Task<bool> IsOriginFromAPaidCustomer(
    string originRequested)
  {
    // Do database look up here to determine if origin should be allowed
    return true;
  }
}

Класс CorsPolicy имеет все свойства для выражения разрешений CORS для предоставления.Используемые здесь значения являются лишь примером, но, по-видимому, они могут динамически заполняться из запроса к базе данных (или из любого другого источника).

Фабрика провайдеров нестандартных политик Второй общий подход к построениюдинамическая политика CORS заключается в создании настраиваемой фабрики поставщиков политик.Это часть платформы CORS, которая получает поставщика политики для текущего запроса.Реализация по умолчанию из Web API использует настраиваемые атрибуты для обнаружения поставщика политики (как вы видели ранее, сам класс атрибута был поставщиком политики).Это еще одна подключаемая часть инфраструктуры CORS, и вы бы внедрили свою собственную фабрику провайдеров политики, если бы хотели использовать подход для политики, отличной от пользовательских атрибутов.

Описанный ранее подход на основе атрибутов обеспечивает неявныйассоциация от запроса к политике.Подход на основе фабрики настраиваемых поставщиков политик отличается от подхода на основе атрибутов, поскольку требует, чтобы ваша реализация предоставляла логику для сопоставления входящего запроса с политикой.Этот подход более грубый, поскольку по сути это централизованный подход для получения политики CORS.

На рисунке 6 показан пример того, как может выглядеть фабрика провайдеров пользовательских политик.Основное внимание в этом примере уделяется реализации интерфейса ICorsPolicyProviderFactory и его метода GetCorsPolicyProvider.

Figure 6 A Custom Policy Provider Factory
public class DynamicPolicyProviderFactory : ICorsPolicyProviderFactory
{
  public ICorsPolicyProvider GetCorsPolicyProvider(
    HttpRequestMessage request)
  {
    var route = request.GetRouteData();
    var controller = (string)route.Values["controller"];
    var corsRequestContext = request.GetCorsRequestContext();
    var originRequested = corsRequestContext.Origin;
    var policy = GetPolicyForControllerAndOrigin(
      controller, originRequested);
    return new CustomPolicyProvider(policy);
  }
  private CorsPolicy GetPolicyForControllerAndOrigin(
   string controller, string originRequested)
  {
    // Do database lookup to determine if the controller is allowed for
    // the origin and create CorsPolicy if it is (otherwise return null)
    var policy = new CorsPolicy();
    policy.Origins.Add(originRequested);
    policy.Methods.Add("GET");
    return policy;
  }
}
public class CustomPolicyProvider : ICorsPolicyProvider
{
  CorsPolicy policy;
  public CustomPolicyProvider(CorsPolicy policy)
  {
    this.policy = policy;
  }
  public Task<CorsPolicy> GetCorsPolicyAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
  {
    return Task.FromResult(this.policy);
  }
}

Основным отличием этого подхода является то, что реализация полностью определяет политику из входящего запроса.На рисунке 6 контроллер и источник могут использоваться для запроса базы данных о значениях политики.Опять же, этот подход является наиболее гибким, но он потенциально требует больше работы для определения политики из запроса.

Чтобы использовать фабрику настраиваемого поставщика политики, необходимо зарегистрировать ее в Web API с помощью метода расширения SetCorsPolicyProviderFactory вконфигурация веб-API:

public static class WebApiConfig
{
  public static void Register(HttpConfiguration config)
  {
    // Other configuration omitted
    config.EnableCors();
    config.SetCorsPolicyProviderFactory(
      new DynamicPolicyProviderFactory());
  }
}

Отладка CORS

Несколько методов приходят на ум для отладки CORS, если (и когда) ваши вызовы AJAX из разных источников не работают.

Клиентская сторона Один из подходов к отладке - просто использовать выбранный HTTP-отладчик (например, Fiddler) и проверять все HTTP-запросы.Вооружившись знаниями, полученными ранее о деталях спецификации CORS, вы обычно можете выяснить, почему конкретному запросу AJAX не предоставлено разрешение, проверив HTTP-заголовки CORS (или их отсутствие).

Другой подходиспользовать инструменты разработчика F12 вашего браузера.Окно консоли в современных браузерах предоставляет полезное сообщение об ошибке, когда AJAX-вызовы не выполняются из-за CORS.

На стороне сервера Платформа CORS сама предоставляет подробные сообщения трассировки с использованием средств трассировки Web API.Пока ITraceWriter зарегистрирован в Web API, платформа CORS будет отправлять сообщения с информацией о выбранном поставщике политики, используемой политике и отправляемых HTTP-заголовках CORS.Для получения дополнительной информации о трассировке веб-API обратитесь к документации по веб-API на MSDN.


Хочу подчеркнуть, что приведенный выше отрывок не является моей работой и полностью относится к Броку Аллену из Thinktecture.

...