Включение запросов между источниками в веб-API ASP.NET 2
Майк Уосон
Это содержимое предназначено для предыдущей версии .NET. Новая разработка должна использовать ASP.NET Core. Дополнительные сведения об использовании веб-API и запросов между источниками (CORS) в ASP.NET Core см. в следующей статье:
Параметры безопасности веб-браузера предотвращают отправку запросов AJAX с веб-страницы к другому домену. Такое ограничение называется политикой одного источника. Эта политика предотвращает чтение вредоносным сайтом конфиденциальных данных с другого сайта. Однако иногда может потребоваться разрешить другим сайтам вызывать ваш веб-API.
Совместное использование ресурсов между источниками (CORS) — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника. С помощью CORS сервер может явным образом разрешить некоторые запросы независимо от источника, а другие — отклонить. CORS является более безопасным и более гибким, чем более ранние методы, такие как JSONP. В этом руководстве показано, как включить CORS в приложении веб-API.
Программное обеспечение, используемое в руководстве
- Visual Studio
- Веб-API 2.2
Введение
В этом руководстве демонстрируется поддержка CORS в веб-API ASP.NET. Сначала мы создадим два проекта ASP.NET — один с именем WebService, на котором размещен контроллер веб-API, а другой — "WebClient", который вызывает веб-службу. Так как два приложения размещаются в разных доменах, запрос AJAX из WebClient в WebService — это запрос между источниками.
Что такое "тот же источник"?
Два URL-адреса имеют одинаковый источник, если они имеют одинаковые схемы, узлы и порты. (RFC 6454)
Эти два URL-адреса имеют одинаковый источник:
http://example.com/foo.html
http://example.com/bar.html
Эти URL-адреса имеют разные источники, отличные от предыдущих двух:
http://example.net
— другой доменhttp://example.com:9000/foo.html
— другой портhttps://example.com/foo.html
— другая схемаhttp://www.example.com/foo.html
— другой поддомен
Примечание.
Internet Explorer не учитывает порт при сравнении источников.
Создание проекта WebService
Примечание.
В этом разделе предполагается, что вы уже знаете, как создавать проекты веб-API. Если нет, см. статью "Начало работы с веб-API ASP.NET".
Запустите Visual Studio и создайте проект веб-приложения ASP.NET (платформа .NET Framework).
В диалоговом окне "Создать ASP.NET веб-приложение" выберите шаблон пустого проекта. В разделе "Добавление папок и основных ссылок" установите флажок веб-API .
Добавьте контроллер веб-API с именем
TestController
со следующим кодом:using System.Net.Http; using System.Web.Http; namespace WebService.Controllers { public class TestController : ApiController { public HttpResponseMessage Get() { return new HttpResponseMessage() { Content = new StringContent("GET: Test message") }; } public HttpResponseMessage Post() { return new HttpResponseMessage() { Content = new StringContent("POST: Test message") }; } public HttpResponseMessage Put() { return new HttpResponseMessage() { Content = new StringContent("PUT: Test message") }; } } }
Приложение можно запустить локально или развернуть в Azure. (На снимках экрана, приведенных в этом руководстве, приложение развертывается в службе приложение Azure веб-приложения.) Чтобы убедиться, что веб-API работает, перейдите к разделу
http://hostname/api/test/
, где имя узла — это домен, в котором развернуто приложение. Вы увидите текст ответа "GET: Тестовое сообщение".
Создание проекта WebClient
Создайте другой проект ASP.NET веб-приложения (платформа .NET Framework) и выберите шаблон проекта MVC. При необходимости выберите "Изменить проверку подлинности без проверки подлинности>". Для этого руководства не требуется проверка подлинности.
В Обозреватель решений откройте файл Views/Home/Index.cshtml. Замените код в этом файле следующим образом:
<div> <select id="method"> <option value="get">GET</option> <option value="post">POST</option> <option value="put">PUT</option> </select> <input type="button" value="Try it" onclick="sendRequest()" /> <span id='value1'>(Result)</span> </div> @section scripts { <script> // TODO: Replace with the URL of your WebService app var serviceUrl = 'http://mywebservice/api/test'; function sendRequest() { var method = $('#method').val(); $.ajax({ type: method, url: serviceUrl }).done(function (data) { $('#value1').text(data); }).fail(function (jqXHR, textStatus, errorThrown) { $('#value1').text(jqXHR.responseText || textStatus); }); } </script> }
Для переменной serviceUrl используйте универсальный код ресурса (URI) приложения WebService.
Запустите приложение WebClient локально или опубликуйте его на другом веб-сайте.
При нажатии кнопки "Попробовать" запрос AJAX отправляется в приложение WebService с помощью метода HTTP, указанного в раскрывающемся списке (GET, POST или PUT). Это позволяет изучить различные запросы между источниками. В настоящее время приложение WebService не поддерживает CORS, поэтому при нажатии кнопки вы получите ошибку.
Примечание.
Если вы просматриваете HTTP-трафик в инструменте, например Fiddler, вы увидите, что браузер отправляет запрос GET, и запрос завершается успешно, но вызов AJAX возвращает ошибку. Важно понимать, что политика одного и того же источника не предотвращает отправку запроса браузером. Вместо этого приложение не будет видеть ответ.
Включение CORS
Теперь давайте включите CORS в приложении WebService. Сначала добавьте пакет NuGet CORS. В Visual Studio в меню "Сервис" выберите NuGet диспетчер пакетов, а затем выберите диспетчер пакетов консоль. В окне консоли диспетчер пакетов введите следующую команду:
Install-Package Microsoft.AspNet.WebApi.Cors
Эта команда устанавливает последний пакет и обновляет все зависимости, включая основные библиотеки веб-API. -Version
Используйте флаг для назначения определенной версии. Пакет CORS требует веб-API 2.0 или более поздней версии.
Откройте файл App_Start/WebApiConfig.cs. Добавьте следующий код в метод WebApiConfig.Register :
using System.Web.Http;
namespace WebService
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// New code
config.EnableCors();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Затем добавьте атрибут [EnableCors] в TestController
класс:
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Cors;
namespace WebService.Controllers
{
[EnableCors(origins: "http://mywebclient.azurewebsites.net", headers: "*", methods: "*")]
public class TestController : ApiController
{
// Controller methods not shown...
}
}
Для параметра источника используйте универсальный код ресурса (URI), в котором развернуто приложение WebClient. Это позволяет выполнять запросы между источниками из WebClient, не разрешая все остальные междоменные запросы. Позже я подробно охарактеризую параметры для [EnableCors] .
Не включайте косую черту вперед в конце URL-адреса источника .
Повторно разверните обновленное приложение WebService. Вам не нужно обновлять WebClient. Теперь запрос AJAX из WebClient должен завершиться успешно. Все методы GET, PUT и POST разрешены.
Принцип работы CORS
В этом разделе описывается, что происходит в запросе CORS на уровне HTTP-сообщений. Важно понять, как работает CORS, чтобы настроить атрибут [EnableCors] правильно и устранить неполадки, если все не работает должным образом.
Спецификация CORS содержит несколько новых заголовков HTTP, которые позволяют выполнять запросы между источниками. Если браузер поддерживает CORS, он автоматически задает эти заголовки для запросов между источниками; В коде JavaScript ничего специального не нужно делать.
Ниже приведен пример запроса между источниками. Заголовок "Origin" предоставляет домен сайта, выполняющего запрос.
GET http://myservice.azurewebsites.net/api/test HTTP/1.1
Referer: http://myclient.azurewebsites.net/
Accept: */*
Accept-Language: en-US
Origin: http://myclient.azurewebsites.net
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Если сервер разрешает запрос, он задает заголовок Access-Control-Allow-Origin. Значение этого заголовка совпадает с заголовком Origin или является подстановочным знаком "*", то есть допускается любой источник.
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Date: Wed, 05 Jun 2013 06:27:30 GMT
Content-Length: 17
GET: Test message
Если ответ не включает заголовок Access-Control-Allow-Origin, запрос AJAX завершается ошибкой. В частности, браузер запрещает запрос. Даже если сервер возвращает успешный ответ, браузер не делает ответ доступным для клиентского приложения.
Предварительные запросы
Для некоторых запросов CORS браузер отправляет дополнительный запрос, называемый "предварительным запросом" перед отправкой фактического запроса ресурса.
Браузер может пропустить предварительный запрос, если заданы следующие условия:
Метод запроса — GET, HEAD или POST, а также
Приложение не задает заголовки запросов, отличные от Accept, Accept-Language, Content-Language, Content-Type или Last-Event-ID, и
Заголовок Content-Type (если задан) является одним из следующих элементов:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
Правило о заголовках запросов применяется к заголовкам, заданным приложением путем вызова setRequestHeader в объекте XMLHttpRequest . (Спецификация CORS вызывает эти заголовки запросов авторов.) Правило не применяется к заголовкам, которые браузер может задать, например User-Agent, Host или Content-Length.
Ниже приведен пример предварительного запроса:
OPTIONS http://myservice.azurewebsites.net/api/test HTTP/1.1
Accept: */*
Origin: http://myclient.azurewebsites.net
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: accept, x-my-custom-header
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)
Host: myservice.azurewebsites.net
Content-Length: 0
Запрос перед полетом использует метод HTTP OPTIONS. Он включает два специальных заголовка:
- Метод Access-Control-Request-Method: метод HTTP, который будет использоваться для фактического запроса.
- Access-Control-Request-Headers: список заголовков запросов, заданных приложением в фактическом запросе. (Опять же, это не включает заголовки, которые задает браузер.)
Ниже приведен пример ответа, предполагающий, что сервер разрешает запрос:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 0
Access-Control-Allow-Origin: http://myclient.azurewebsites.net
Access-Control-Allow-Headers: x-my-custom-header
Access-Control-Allow-Methods: PUT
Date: Wed, 05 Jun 2013 06:33:22 GMT
Ответ включает заголовок Access-Control-Allow-Methods, который перечисляет разрешенные методы, а также заголовок Access-Control-Allow-Headers, который содержит допустимые заголовки. Если предварительный запрос выполнен успешно, браузер отправляет фактический запрос, как описано ранее.
Средства, которые обычно используются для тестирования конечных точек с предварительными запросами OPTIONS, по умолчанию не отправляют необходимые заголовки OPTIONS. Убедитесь, что Access-Control-Request-Method
заголовки и Access-Control-Request-Headers
заголовки отправляются с запросом и что заголовки OPTIONS достигают приложения через СЛУЖБЫ IIS.
Чтобы настроить IIS, чтобы разрешить приложению ASP.NET получать и обрабатывать запросы OPTION, добавьте следующую конфигурацию в файл web.config приложения в <system.webServer><handlers>
разделе:
<system.webServer>
<handlers>
<remove name="ExtensionlessUrlHandler-Integrated-4.0" />
<remove name="OPTIONSVerbHandler" />
<add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
</handlers>
</system.webServer>
OPTIONSVerbHandler
Удаление не позволяет службам IIS обрабатывать запросы OPTIONS. Замена ExtensionlessUrlHandler-Integrated-4.0
запросов OPTIONS обеспечивает доступ к приложению, так как регистрация модуля по умолчанию разрешает только запросы GET, HEAD, POST и DEBUG с URL-адресами без расширения.
Правила области для [EnableCors]
Вы можете включить CORS на действие, на контроллер или глобально для всех контроллеров веб-API в приложении.
Каждое действие
Чтобы включить CORS для одного действия, задайте атрибут [EnableCors] в методе действия. Следующий пример включает CORS только для GetItem
метода.
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage PutItem(int id) { ... }
}
На контроллер
Если в классе контроллера задано значение [EnableCors] , оно применяется ко всем действиям на контроллере. Чтобы отключить CORS для действия, добавьте атрибут [DisableCors] в действие. Следующий пример включает CORS для каждого метода, кроме PutItem
.
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "*")]
public class ItemsController : ApiController
{
public HttpResponseMessage GetAll() { ... }
public HttpResponseMessage GetItem(int id) { ... }
public HttpResponseMessage Post() { ... }
[DisableCors]
public HttpResponseMessage PutItem(int id) { ... }
}
Глобально
Чтобы включить CORS для всех контроллеров веб-API в приложении, передайте экземпляр EnableCorsAttribute в метод EnableCors :
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
var cors = new EnableCorsAttribute("www.example.com", "*", "*");
config.EnableCors(cors);
// ...
}
}
Если атрибут задан в нескольких областях, порядок приоритета будет следующим:
- Действие
- Контроллер
- Глобальный
Настройка разрешенных источников
Параметр источника атрибута [EnableCors] указывает, какие источники разрешены для доступа к ресурсу. Значение — это разделенный запятыми список разрешенных источников.
[EnableCors(origins: "http://www.contoso.com,http://www.example.com",
headers: "*", methods: "*")]
Можно также использовать подстановочное значение "*", чтобы разрешить запросы из любых источников.
Прежде чем разрешать запросы из любого источника, следует тщательно рассмотреть этот вопрос. Это означает, что буквально любой веб-сайт может выполнять вызовы AJAX к веб-API.
// Allow CORS for all origins. (Caution!)
[EnableCors(origins: "*", headers: "*", methods: "*")]
Установка разрешенных методов HTTP
Параметр методов атрибута [EnableCors] указывает, какие методы HTTP разрешены для доступа к ресурсу. Чтобы разрешить все методы, используйте подстановочное значение "*". В следующем примере разрешены только запросы GET и POST.
[EnableCors(origins: "http://www.example.com", headers: "*", methods: "get,post")]
public class TestController : ApiController
{
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
public HttpResponseMessage Put() { ... }
}
Настройка заголовков разрешенных запросов
В этой статье описано ранее, как предварительный запрос может включать заголовок Access-Control-Request-Headers, в котором перечислены заголовки HTTP, заданные приложением (так называемые заголовки запросов автора). Параметр заголовков атрибута [EnableCors] указывает, какие заголовки запросов автора разрешены. Чтобы разрешить любые заголовки, задайте для заголовков значение "*". Чтобы разрешить определенные заголовки, задайте заголовки в разделенный запятыми список разрешенных заголовков:
[EnableCors(origins: "http://example.com",
headers: "accept,content-type,origin,x-my-header", methods: "*")]
Однако браузеры не полностью согласованы в том, как они задают заголовки Access-Control-Request-Headers. Например, Chrome в настоящее время включает "источник". FireFox не включает стандартные заголовки, такие как Accept, даже если приложение задает их в скрипте.
Если для заголовков задано значение "*", следует включить по крайней мере "accept", "content-type" и "origin", а также любые пользовательские заголовки, которые требуется поддерживать.
Настройка заголовков разрешенных ответов
По умолчанию браузер не предоставляет все заголовки ответа приложению. Заголовки ответов, доступные по умолчанию:
- Cache-Control
- Content-Language;
- Тип контента
- Срок действия истекает
- Last-Modified
- Pragma
Спецификация CORS вызывает эти простые заголовки ответа. Чтобы сделать другие заголовки доступными для приложения, задайте параметр exposedHeaders [EnableCors].
В следующем примере метод контроллера Get
задает настраиваемый заголовок с именем X-Custom-Header. По умолчанию браузер не будет предоставлять этот заголовок в запросе между источниками. Чтобы сделать заголовок доступным, включите "X-Custom-Header" в exposedHeaders.
[EnableCors(origins: "*", headers: "*", methods: "*", exposedHeaders: "X-Custom-Header")]
public class TestController : ApiController
{
public HttpResponseMessage Get()
{
var resp = new HttpResponseMessage()
{
Content = new StringContent("GET: Test message")
};
resp.Headers.Add("X-Custom-Header", "hello");
return resp;
}
}
Передача учетных данных в запросах между источниками
Учетные данные требуют специальной обработки в запросе CORS. По умолчанию браузер не отправляет учетные данные с запросом между источниками. Учетные данные включают файлы cookie, а также схемы проверки подлинности HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение true XMLHttpRequest.withCredentials .
Использование XMLHttpRequest напрямую:
var xhr = new XMLHttpRequest();
xhr.open('get', 'http://www.example.com/api/test');
xhr.withCredentials = true;
В jQuery:
$.ajax({
type: 'get',
url: 'http://www.example.com/api/test',
xhrFields: {
withCredentials: true
}
Кроме того, сервер должен разрешить учетные данные. Чтобы разрешить учетные данные между источниками в веб-API, задайте для свойства SupportsCredentials значение true для атрибута [EnableCors]:
[EnableCors(origins: "http://myclient.azurewebsites.net", headers: "*",
methods: "*", SupportsCredentials = true)]
Если это свойство имеет значение true, http-ответ будет содержать заголовок Access-Control-Allow-Credentials. Этот заголовок сообщает браузеру, что сервер разрешает учетные данные для запроса между источниками.
Если браузер отправляет учетные данные, но ответ не включает допустимый заголовок Access-Control-Allow-Credentials, браузер не будет предоставлять ответ приложению, а запрос AJAX завершается ошибкой.
Будьте осторожны с параметром SupportsCredentials значение true, так как это означает, что веб-сайт в другом домене может отправлять учетные данные пользователя, вошедшего в систему, в веб-API от имени пользователя без уведомления пользователя. Спецификация CORS также указывает, что значение " *" является недопустимым, если значение "ПоддерживаетCredentials " имеет значение true.
Пользовательские поставщики политик CORS
Атрибут [EnableCors] реализует интерфейс ICorsPolicyProvider . Вы можете предоставить собственную реализацию, создав класс, производный от Атрибута и реализующий ICorsPolicyProvider.
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false)]
public class MyCorsPolicyAttribute : Attribute, ICorsPolicyProvider
{
private CorsPolicy _policy;
public MyCorsPolicyAttribute()
{
// Create a CORS policy.
_policy = new CorsPolicy
{
AllowAnyMethod = true,
AllowAnyHeader = true
};
// Add allowed origins.
_policy.Origins.Add("http://myclient.azurewebsites.net");
_policy.Origins.Add("http://www.contoso.com");
}
public Task<CorsPolicy> GetCorsPolicyAsync(HttpRequestMessage request)
{
return Task.FromResult(_policy);
}
}
Теперь вы можете применить атрибут в любом месте, которое будет помещено [EnableCors].
[MyCorsPolicy]
public class TestController : ApiController
{
.. //
Например, настраиваемый поставщик политики CORS может считывать параметры из файла конфигурации.
В качестве альтернативы использованию атрибутов можно зарегистрировать объект ICorsPolicyProviderFactory , который создает объекты ICorsPolicyProvider .
public class CorsPolicyFactory : ICorsPolicyProviderFactory
{
ICorsPolicyProvider _provider = new MyCorsPolicyProvider();
public ICorsPolicyProvider GetCorsPolicyProvider(HttpRequestMessage request)
{
return _provider;
}
}
Чтобы задать метод расширения ICorsPolicyProviderFactory, вызовите метод расширения SetCorsPolicyProviderFactory при запуске следующим образом:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.SetCorsPolicyProviderFactory(new CorsPolicyFactory());
config.EnableCors();
// ...
}
}
Поддержка веб-браузеров
Пакет CORS веб-API — это серверная технология. Браузер пользователя также должен поддерживать CORS. К счастью, текущие версии всех основных браузеров включают поддержку CORS.