CORS (cross-origin resource sharing) — спецификация W3C (обычно рассматриваемая как часть HTML5), которая позволяет JavaScript преодолевать ограничение безопасности политики «тот же источник», накладываемое браузерами. Политика «тот же источник» (same-origin policy) означает, что ваш JavaScript-код может выдавать обратные AJAX-вызовы только к тому же источнику, содержащему веб-страницу (где источник определяется как комбинация имени хоста, протокола и номера порта). Например, JavaScript в веб-странице с http://foo.com не может делать AJAX-вызовы http://bar.com (или http://www.foo.com, https://foo.com, http://foo.com:999 и т. д.).
CORS ослабляет это ограничение, позволяя серверам указывать, каким источникам разрешено вызывать их. CORS вводится в действие браузерами, но должна быть реализована на сервере, и в новейших выпусках ASP.NET Web API 2 имеется полная поддержка CORS. С помощью Web API 2 вы можете сконфигурировать политику, которая разрешает JavaScript-клиентам из другого источника обращаться к вашим API.
Основы CORS
Чтобы использовать новые средства CORS в Web API, полезно понимать детали самой CORS, так как реализация в Web API полностью соответствует этой спецификации. Эти детали сейчас могут показаться излишними, но они пригодятся потом, когда вам потребуется разобраться в доступных настройках Web API, а также при отладке CORS.
Механика CORS такова, что, когда JavaScript пытается инициировать AJAX-вызов с запросом происхождения, браузер «спросит» у сервера, разрешено ли это, для чего отправит заголовки в HTTP-запросе (например, Origin). Сервер сообщит, что именно разрешено, возвращая HTTP-заголовки в ответе (скажем, Access-Control-Allow-Origin). Эта проверка разрешения выполняется для каждого индивидуального URL, вызываемого клиентом, а это означает, что у разных URL могут быть разные разрешения.
Помимо источника, CORS позволяет серверу указывать, какие HTTP-методы разрешены, какие HTTP-заголовки запроса может посылать клиент, какие HTTP-заголовки ответа может читать клиент и разрешено ли браузеру автоматически посылать или принимать удостоверения (файлы cookie или заголовки авторизации). Дополнительные заголовки запроса и ответа сообщают, какие из этих средств разрешены. Данные заголовки кратко описаны в табл. 1 (заметьте, что для некоторых средств заголовки в запросе не посылаются — только в ответе).
Табл. 1. HTTP-заголовки CORS
Разрешение/средство | Заголовок запроса | Заголовок ответа |
Источник | Origin | Access-Control-Allow-Origin |
HTTP-метод | Access-Control-Request-Method | Access-Control-Allow-Method |
Заголовки запроса | Access-Control-Request-Headers | Access-Control-Allow-Headers |
Заголовки ответа | | Access-Control-Expose-Headers |
Удостоверения | | Access-Control-Allow-Credentials |
Кеширование предварительного ответа | | Access-Control-Max-Age |
Браузеры могут запрашивать у сервера эти разрешения двумя способами: простыми CORS-запросами и предварительными (preflight CORS requests).
Простые CORS-запросы Вот пример такого запроса:
POST http://localhost/WebApiCorsServer/Resources/ HTTP/1.1
Host: localhost
Accept: */*
Origin: http://localhost:55912
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
value1=foo&value2=5
And the response:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}
Это запрос происхождения с http://localhost:55912 на http://localhost, и браузер добавляет HTTP-заголовок Origin в запрос, указывая серверу вызов источника. Сервер отвечает заголовком Access-Control-Allow-Origin, сообщая, что этот источник разрешен. Браузер вводит в действие политику сервера, и JavaScript получит нормальный успешный обратный вызов.
Сервер может дать ответ либо с точным значением источника из запроса, либо значением «*», указывающим, что разрешен любой источник. Если сервер не разрешил вызываемый источник, тогда заголовок Access-Control-Allow-Origin просто отсутствовал бы и JavaScript получил бы ошибку обратного вызова.
Заметьте, что при простом CORS-запросе вызов на сервере все равно сработает. Это может показаться удивительным, если вы еще только осваиваете CORS, но такое поведение ничем не отличается от сценария, где браузер конструировал элемент <form> и выдавал обычный POST-запрос. CORS не предотвращает запуск вызова на сервере; вместо этого он препятствует вызвавшему JavaScript-коду получить результаты. Если вам нужно запретить вызвавшему коду запуск вызова сервера, то вы могли бы реализовать ту или иную форму авторизации в вашем серверном коде (возможно, с помощью атрибута фильтра авторизации [Authorize]).
Предыдущий пример известен как простой CORS-запрос, поскольку тип AJAX-вызова от клиента был либо GET, либо POST, Content-Type был чем-то из application/x-www-form-urlencoded, multipart/form-data или text/plain, и никаких дополнительных заголовков запросы не отсылалось. Если бы AJAX-вызов был другим HTTP-методом, Content-Type имел бы какое-то другое значение или клиент хотел бы отправить дополнительные заголовки запроса, тогда запрос считался бы предварительным (preflight request). Механика предварительных запросов несколько иная.
С помощью Web API 2 вы можете сконфигурировать политику, которая разрешает JavaScript-клиентам из другого источника обращаться к вашим API.
Предварительные CORS-запросы Если AJAX-вызов не является простым запросом, тогда необходим предварительный CORS-запрос — дополнительный HTTP-запрос к серверу для получения разрешения. Этот предварительный запрос выполняется браузером автоматически и использует HTTP-метод OPTIONS. Если сервер успешно отвечает на предварительный запрос и выдает разрешение, тогда браузер выполнит собственно AJAX-вызов, исходящий из JavaScript-кода.
Если вам важна производительность (а кому она не важна?), то результат этого предварительного запроса можно кешировать браузером, включив заголовок Access-Control-Max-Age в ответ на предварительный запрос. Значение содержит число секунд, в течение которого можно кешировать разрешения.
Вот пример предварительного CORS-запроса:
OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: PUT
Origin: http://localhost:55912
Access-Control-Request-Headers: content-type
Accept: */*
И ответа на него:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Headers: content-typeAccess-Control-Max-Age: 600
Это сам AJAX-запрос:
PUT http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Content-Length: 27
Accept: application/json, text/javascript, */*; q=0.01
Origin: http://localhost:55912
Content-Type: application/json
{"value1":"foo","value2":5}
И AJAX-ответ:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Content-Length: 27
{"Value1":"foo","Value2":5}
Заметьте, что в этом примере предварительный CORS-запрос инициируется из-за того, что HTTP-методом является PUT и клиенту нужно отправить заголовок Content-Type, указывающий, что запрос содержит application/json. В предварительном запросе (помимо Origin) используются заголовки Access-Control-Request-Method и Access-Control-Request-Headers — с их помощью запрашивается разрешение для типа HTTP-метода и дополнительного заголовка, который хотел бы отправить клиент.
Сервер выдает разрешение (и задает длительность кеширования результата предварительного запроса), а затем браузер разрешает собственно AJAX-вызов. Если сервер не выдал разрешение на использование любой из запрошенной функциональности, в ответе отсутствует соответствующий заголовок, AJAX-вызов выполнить не удастся, а вместо этого будет запущен обратный вызов JavaScript, инициируемый при ошибках.
Предыдущие HTTP-запросы и ответы передавались и принимались с применением Firefox. Если бы использовался Internet Explorer, вы заметили бы, что запрашивается дополнительный заголовок Accept. А в случае Chrome дополнительно запрашивались бы заголовки и Accept, и Origin. Любопытно, что Accept или Origin в Access-Control-Allow-Headers вы не увидели бы, так как спецификация указывает, что они подразумеваются и могут быть опущены (что и делает Web API). Сейчас идут споры, нужно ли вообще запрашивать Origin и Accept, но, учитывая, как работают нынешние браузеры, в вашу политику Web API CORS скорее всего потребуется включать эти заголовки. Жаль, что браузеры от разных поставщиков по-разному трактуют эту спецификацию.
Заголовки ответа Несложно выдать клиенту разрешение на доступ к заголовкам ответа, используя заголовок ответа Access-Control-Expose-Headers. Вот пример HTTP-ответа, который позволяет вызывающему JavaScript-кода обращаться к пользовательскому заголовку ответа bar:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Expose-Headers: bar
bar: a bar value
Content-Length: 27
{"Value1":"foo","Value2":5}
Вероятно, самый запутанный аспект CORS относится к удостоверениям и аутентификации.
JavaScript-клиент может просто использовать функцию XMLHttpRequest getResponseHeader для чтения значения. Вот пример с применением jQuery:
$.ajax({
url: "http://localhost/WebApiCorsServer/Resources/1",
// Остальные параметры опущены
}).done(function (data, status, xhr) {
var bar = xhr.getResponseHeader("bar");
alert(bar);
});
Удостоверения и аутентификация Вероятно, самый запутанный аспект CORS относится к удостоверениям и аутентификации. В принципе, аутентификация в Web API осуществляется либо через cookie, либо через заголовок Authorization (есть и другие способы, но эти два — самые распространенные). В обычных обстоятельствах браузер, если установлен один из этих элементов, будет неявно передавать соответствующие значения серверу при последующих запросах. Однако в AJAX-вызовах с запросом происхождения эта неявная передача значений должна быть заменена явным запросом в JavaScript (через флаг withCredentials в XMLHttpRequest) и явным образом разрешена политикой CORS сервера (через заголовок ответа Access-Control-Allow-Credentials).
Ниже дан пример JavaScript-клиента, устанавливающего флаг withCredentials с помощью jQuery:
$.ajax({
url: "http://localhost/WebApiCorsServer/Resources/1",
xhrFields: {
withCredentials: true
}
// Прочие параметры опущены
});
Флаг withCredentials делает две вещи: если сервер генерирует cookie, браузер может принять его; если у браузера есть cookie, он может отправить его серверу.
Вот пример HTTP-ответа, разрешающего удостоверения:
HTTP/1.1 200 OK
Set-Cookie: foo=1379020091825
Access-Control-Allow-Origin: http://localhost:55912
Access-Control-Allow-Credentials: true
Заголовок ответа Access-Control-Allow-Credentials делает две вещи: если с ответом посылается cookie, браузер может принять его; если браузер отправил cookie с запросом, JavaScript-клиент может принять результаты вызова. Иначе говоря, если клиент выставляет withCredentials, то в JavaScript увидит только успешный обратный вызов при условии, что сервер (в ответе) согласился принимать удостоверения. Если withCredentials установлен, а сервер не разрешил удостоверения, клиент не получит доступа к результатам и будет запущен обратный вызов, сообщающий об ошибке.
Тот же набор правил и поведений применяется, если заголовок Authorization используется вместо файлов cookie (например, в случае аутентификации Basic или Integrated Windows). Любопытно, что при использовании удостоверений и заголовка Authorization сервер не обязательно явным образом выдает заголовок Authorization в CORS-заголовке ответа Access-Control-Allow-Headers.
Заметьте, что в случае CORS-заголовка ответа Access-Control-Allow-Credentials, если его выдает сервер, нельзя использовать символ подстановки «*» в заголовке Access-Control-Allow-Origin. Вместо этого спецификация CORS требует явного указания источника. Инфраструктура Web API обрабатывает все это за вас, но я упоминаю об этом здесь потому, что это поведение важно понимать при отладке.
Кроме того, в обсуждении удостоверений и аутентификации важно отметить интересную особенность. До этого момента все обсуждение относилось к сценарию, где браузер неявно посылает удостоверения. Однако JavaScript-клиент может явным образом отправлять удостоверения (и вновь, как правило, через заголовок Authorization). Если это так, тогда ни одно из упомянутых выше правил и поведений, связанных с удостоверениями, не действует.
Для этого сценария клиент мог бы явным образом установить заголовок Authorization в запросе, и ему не потребовалось бы устанавливать withCredentials в XMLHttpRequest. Этот заголовок инициировал бы предварительный запрос, а серверу понадобилось бы разрешить заголовок Authorization в CORS-заголовке ответа Access-Control-Allow-Headers. Кроме того, серверу не нужно было бы выдавать CORS-заголовок ответа Access-Control-Allow-Credentials.
Вот как выглядел бы клиентский код, явным образом задающий заголовок Authorization:
$.ajax({
url: "http://localhost/WebApiCorsServer/Resources/1",
headers: {
"Authorization": "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3Mi..."
}
// Прочие параметры опущены
});
А это предварительный запрос:
OPTIONS http://localhost/WebApiCorsServer/Resources/1 HTTP/1.1
Host: localhost
Access-Control-Request-Method: GET
Origin: http://localhost:55912
Access-Control-Request-Headers: authorization
Accept: */*
И ответ на такой запрос:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: authorization
Явное задание значение маркера в заголовке Authorization — более безопасный подход к аутентификации, так как он предотвращает вероятность атак с подделкой межсайтовых запросов (cross-site request forgery, CSRF). Этот подход вы можете увидеть в новых шаблонах Single-Page Application (SPA) в Visual Studio 2013.
Теперь, когда вы знаете основы CORS на уровне HTTP, я покажу, как использовать новую инфраструктуру CORS для генерации этих заголовков из Web API.
Поддержка CORS в Web API 2
Поддержка CORS в Web API — полноценная инфраструктура, позволяющая приложению определять разрешения для CORS-запросов. Эта инфраструктура построена вокруг концепции политики, которая дает возможность указывать, какие именно средства CORS следует разрешать какому-либо запросу, адресованному вашему приложению.
Чтобы получить доступ к инфраструктуре CORS, вы должны первым делом добавить ссылки на библиотеки CORS из своего приложения Web API (по умолчанию ни в одном шаблоне Web API в Visual Studio 2013 ссылок на эти библиотеки нет). Инфраструктура CORS для Web API доступна через NuGet в виде пакета Microsoft.AspNet.WebApi.Cors. Если вы не используете NuGet, то CORS также доступна как часть 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-заголовки ответов, она должна анализировать каждый запрос, адресованный приложению. В Web API есть точка расширения для такого перехвата через обработчики сообщений. Соответственно инфраструктура Web API CORS реализует свой обработчик сообщений — CorsMessageHandler. Для CORS-запросов он будет сверяться с политикой, выраженной в атрибуте для запускаемого метода, и генерировать должные CORS-заголовки ответов.
EnableCorsAttribute Этот класс позволяет приложению выражать его политику CORS. В нем есть перегруженный конструктор, который принимает либо три, либо четыре параметра. Эти параметры (по порядку) перечислены ниже.
- Список допустимых источников.
- Список допустимых заголовков запроса.
- Список разрешенных HTTP-методов.
- Список разрешенных заголовков ответа (не обязательный параметр).
Есть также свойство, разрешающее удостоверения (SupportsCredentials), и еще одно для указания длительности кеширования предварительных запросов (PreflightMaxAge).
На рис. 1 показано, как применять атрибут EnableCors к индивидуальным методам в контроллере. Значения, используемые для различных параметров политики CORS, должны соответствовать CORS-запросам и ответам, приведенным в предыдущих примерах.
Рис. 1. Применение атрибута EnableCors к методам операций
public class ResourcesController : ApiController
{
[EnableCors("http://localhost:55912", // источник
null, // заголовки запроса
"GET", // HTTP-методы
"bar", // заголовки ответа
SupportsCredentials=true // удостоверения
// разрешены
)]
public HttpResponseMessage Get(int id)
{
var resp = Request.CreateResponse(HttpStatusCode.NoContent);
resp.Headers.Add("bar", "a bar value");
return resp;
}
[EnableCors("http://localhost:55912", // источник
"Accept, Origin, Content-Type", // заголовки запроса
"PUT", // HTTP-методы
PreflightMaxAge=600 // время кеширования
// предварительного
// запроса
)]
public HttpResponseMessage Put(Resource data)
{
return Request.CreateResponse(HttpStatusCode.OK, data);
}
[EnableCors("http://localhost:55912", // источник
"Accept, Origin, Content-Type", // заголовки запроса
"POST", // HTTP-методы
PreflightMaxAge=600 // время кеширования
// предварительного
// запроса
)]
public HttpResponseMessage Post(Resource data)
{
return Request.CreateResponse(HttpStatusCode.OK, data);
}
}
Заметьте, что каждый параметр конструктора является строковым. Несколько значений указываются списком, разделяемым запятыми (как в списке разрешенных заголовков запроса на рис. 1). Если вы хотите разрешить все источники, заголовки запросов или HTTP-методы, можете использовать «*» в качестве значения (однако заголовки ответа все равно задаются явным образом).
Помимо применения атрибута 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-разрешений.
Если у вас есть другие методы в контроллере, где вы не хотите разрешать CORS, вы можете прибегнуть к одному из двух вариантов. Вы можете указывать явные значения в списке HTTP-методов (рис. 2) или оставить символ подстановки, но исключить метод Delete атрибутом DisableCors (рис. 3).
Рис. 2. Применение явных значений для 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 не разрешен, так как Delete
// не включен в список методов выше
public HttpResponseMessage Delete(int id)
{
return Request.CreateResponse(HttpStatusCode.NoContent);
}
}
Рис. 3. Использование атрибута 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 не разрешен из-за атрибута [DisableCors]
[DisableCors]
public HttpResponseMessage Delete(int id)
{
return Request.CreateResponse(HttpStatusCode.NoContent);
}
}
CorsMessageHandler Этот обработчик должен быть включен, чтобы инфраструктура CORS перехватывала запросы для оценки на соответствие политике CORS и генерировала CORS-заголовки ответов. Этот обработчик сообщений обычно включается в классе конфигурации приложения Web API вызовом метода расширения EnableCors:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Прочие параметры конфигурации опущены
...
config.EnableCors();
}
}
Если вы хотите предоставить глобальную политику CORS, то можете передать экземпляр класса EnableCorsAttribute как параметр метода EnableCors. Например, следующий код установит глобально в приложении разрешительную политику CORS:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Прочие параметры конфигурации опущены
...
config.EnableCors(new EnableCorsAttribute("*", "*", "*"));
}
}
Как и в случае любого обработчика сообщений, CorsMessageHandler можно в качестве альтернативы зарегистрировать для каждого маршрута отдельно, а не глобально.
Вот и все, что касается основ готовой инфраструктуры CORS в ASP.NET Web API 2. Одна из приятных особенностей этой инфраструктуры в том, что она расширяема для более динамичных сценариев, которые я рассмотрю в следующих разделах.
Настройка политики
Из предыдущих примеров должно быть ясно, что список источников (если не используется символ подстановки) является статическим, и он компилируется в код Web API. Хотя это приемлемо на этапе разработки или в специфических сценариях, так поступать нельзя, если список источников (или других разрешений) требуется определять динамически (скажем, по информации из базы данных).
К счастью, инфраструктуру CORS в Web API можно легко расширить для поддержки динамического списка источников. По сути, эта инфраструктура настолько гибка, что существуют два основных подхода к настройке генерации политики.
Собственный атрибут политики CORS Один из подходов к поддержке динамической политики CORS — разработка собственного класса атрибута, способного генерировать политику на основе какого-либо источника данных. Этот класс можно использовать вместо класса EnableCorsAttribute, предоставляемого Web API. Данный подход очень прост и сохраняет возможность избирательного применения атрибута к конкретным классам и методам.
Чтобы реализовать этот подход, вы просто создаете собственный атрибут, аналогичный существующему классу EnableCorsAttribute. Основное внимание следует уделить интерфейсу ICorsPolicyProvider, который отвечает за создание экземпляра CorsPolicy для данного запроса. Пример приведен на рис. 4.
Рис. 4. Собственный атрибут политики 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))
{
// Разрешаем CORS-запрос
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)
{
// Здесь ищем в базе данных информацию о том,
// следует ли разрешить данный источник
return true;
}
}
В классе CorsPolicy есть все свойства для выражения разрешений CORS, которые вы хотите выдать. Используемые здесь значения — не более чем пример, но ясно, что их можно было бы динамически получать из запроса к базе данных (или от любого другого источника).
Собственная фабрика провайдеров политики Второй основной подход к формированию динамической политики CORS — создание собственной фабрики провайдеров политики (custom policy provider factory). Это часть инфраструктуры CORS, которая получает провайдер политики для текущего запроса. Реализация по умолчанию в Web API использует собственные атрибуты для обнаружения провайдера политики (как вы уже видели, провайдером политики был сам класс атрибута). Это еще один подключаемый кусок инфраструктуры CORS, и вы должны реализовать свою фабрику провайдеров политики, если хотите использовать этот подход.
Подход на основе атрибута, описанный ранее, обеспечивает неявное сопоставление запроса с политикой. Подход с собственной фабрикой провайдеров политики отличается от подхода на основе атрибута, так как требует, чтобы ваша реализация предоставляла логику для сопоставления входящего запроса с какой-либо политикой. Этот подход менее избирательный и фактически является централизованным способом получения политики CORS.
Рис. 5 показывает пример того, как может выглядеть собственная фабрика провайдеров политики. Центральное место в этом примере занимает реализация интерфейса ICorsPolicyProviderFactory и его метода GetCorsPolicyProvider.
Рис. 5. Собственная фабрика провайдеров политики
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)
{
// Ищем по базе данных, чтобы определить, разрешено ли
// контроллеру иметь дело с данным источником,
// и, если да, создаем CorsPolicy (иначе возвращаем 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);
}
}
Основное отличие этого подхода в том, что ответственность за определение политики на основе входящего запроса возлагается целиком на вашу реализацию. На рис. 5 контроллер и источник (origin) можно было бы использовать для запроса от базы данных значений параметров политики. Этот подход самый гибкий, но потенциально требует больше работы для определения политики из запроса.
Вклад членов сообщества
ASP.NET Web API — инфраструктура с открытым исходным кодом и часть более крупного набора инфраструктур с открытым исходным кодом под собирательным названием «ASP.NET Web Stack», в который также входят MVC, Web Pages и др.
Эти инфраструктуры используются для построения платформы ASP.NET и курируются группой ASP.NET в Microsoft. Как куратор платформы с исходным открытым кодом группа ASP.NET приветствует вклад членов сообщества, и реализация CORS в Web API является одним из таких вкладов.
Изначально она была разработана Броком Алленом (Brock Allen) как часть библиотеки защиты thinktecture IdentityModel (thinktecture.github.io).
Чтобы задействовать собственную фабрику провайдеров политики, вы должны зарегистрировать ее в Web API через метод расширения SetCorsPolicyProviderFactory в конфигурации Web API:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Прочие параметры конфигурации опущены
...
config.EnableCors();
config.SetCorsPolicyProviderFactory(
new DynamicPolicyProviderFactory());
}
}
Отладка CORS
Если ваши AJAX-вызовы с запросом происхождения не работают, на ум приходит несколько методик отладки CORS.
Клиентская сторона Один из подходов к отладке — простое использование HTTP-отладчика по вашему выбору (скажем, Fiddler) и анализ всех HTTP-запросов. Вооружившись знанием деталей спецификации CORS, как правило, удается отсортировать, какой именно AJAX-запрос не получает разрешение; для этого достаточно проанализировать HTTP-заголовки CORS (или отсутствие оных).
Другой подход — применение инструментария разработчика, вызываемого нажатием клавиши F12 в вашем браузере. Консольное окно в современных браузерах выводит полезное сообщение об ошибке, когда AJAX-вызовы терпят неудачу из-за CORS.
Серверная сторона Сама инфраструктура CORS предоставляет детализированные трассировочные сообщения, используя средства трассировки в Web API. Если ITraceWriter зарегистрирован в Web API, инфраструктура CORS будет генерировать сообщения с информацией о выбранном провайдере политики, применяемой политике и генерируемых HTTP-заголовках CORS. Подробнее о трассировке Web API см. документацию Web API в MSDN.
В высшей степени востребованная функциональность
CORS уже какое-то время является в высшей степени востребованной функциональностью, и теперь она наконец встроена в Web API. В этой статье были подробно рассмотрены детали самой CORS, и эти знания крайне важны в реализации и отладке CORS. С помощью этих знаний вы сможете легко использовать поддержку CORS в Web API, чтобы разрешить в своем приложении вызовы с запросом происхождения.