Поделиться через


Включение запросов между источниками в веб-API ASP.NET 2

Майк Уосон

Это содержимое предназначено для предыдущей версии .NET. Новая разработка должна использовать ASP.NET Core. Дополнительные сведения об использовании веб-API и запросов между источниками (CORS) в ASP.NET Core см. в следующей статье:

Параметры безопасности веб-браузера предотвращают отправку запросов AJAX с веб-страницы к другому домену. Такое ограничение называется политикой одного источника. Эта политика предотвращает чтение вредоносным сайтом конфиденциальных данных с другого сайта. Однако иногда может потребоваться разрешить другим сайтам вызывать ваш веб-API.

Совместное использование ресурсов между источниками (CORS) — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника. С помощью CORS сервер может явным образом разрешить некоторые запросы независимо от источника, а другие — отклонить. CORS является более безопасным и более гибким, чем более ранние методы, такие как JSONP. В этом руководстве показано, как включить CORS в приложении веб-API.

Программное обеспечение, используемое в руководстве

Введение

В этом руководстве демонстрируется поддержка 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".

  1. Запустите Visual Studio и создайте проект веб-приложения ASP.NET (платформа .NET Framework).

  2. В диалоговом окне "Создать ASP.NET веб-приложение" выберите шаблон пустого проекта. В разделе "Добавление папок и основных ссылок" установите флажок веб-API .

    Диалоговое окно создания проекта ASP.NET в Visual Studio

  3. Добавьте контроллер веб-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")
                };
            }
        }
    }
    
  4. Приложение можно запустить локально или развернуть в Azure. (На снимках экрана, приведенных в этом руководстве, приложение развертывается в службе приложение Azure веб-приложения.) Чтобы убедиться, что веб-API работает, перейдите к разделу http://hostname/api/test/, где имя узла — это домен, в котором развернуто приложение. Вы увидите текст ответа "GET: Тестовое сообщение".

    Веб-браузер, показывающий тестовое сообщение

Создание проекта WebClient

  1. Создайте другой проект ASP.NET веб-приложения (платформа .NET Framework) и выберите шаблон проекта MVC. При необходимости выберите "Изменить проверку подлинности без проверки подлинности>". Для этого руководства не требуется проверка подлинности.

    Шаблон MVC в диалоговом окне

  2. В Обозреватель решений откройте файл 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.

  3. Запустите приложение WebClient локально или опубликуйте его на другом веб-сайте.

При нажатии кнопки "Попробовать" запрос AJAX отправляется в приложение WebService с помощью метода HTTP, указанного в раскрывающемся списке (GET, POST или PUT). Это позволяет изучить различные запросы между источниками. В настоящее время приложение WebService не поддерживает CORS, поэтому при нажатии кнопки вы получите ошибку.

Ошибка

Примечание.

Если вы просматриваете HTTP-трафик в инструменте, например Fiddler, вы увидите, что браузер отправляет запрос GET, и запрос завершается успешно, но вызов AJAX возвращает ошибку. Важно понимать, что политика одного и того же источника не предотвращает отправку запроса браузером. Вместо этого приложение не будет видеть ответ.

Веб-отладчик Fiddler с веб-запросами

Включение 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);
        // ...
    }
}

Если атрибут задан в нескольких областях, порядок приоритета будет следующим:

  1. Действие
  2. Контроллер
  3. Глобальный

Настройка разрешенных источников

Параметр источника атрибута [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.