Включение запросов CORS в ASP.NET Core

Примечание.

Это не последняя версия этой статьи. В текущем выпуске см . версию .NET 8 этой статьи.

Предупреждение

Эта версия ASP.NET Core больше не поддерживается. Дополнительные сведения см. в статье о политике поддержки .NET и .NET Core. В текущем выпуске см . версию .NET 8 этой статьи.

Внимание

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

В текущем выпуске см . версию .NET 8 этой статьи.

Авторы: Рик Андерсон (Rick Anderson) и Кирк Ларкин (Kirk Larkin)

В этой статье показано, как Cross-O rigin Resource Sharing (CORS) включен в приложении ASP.NET Core.

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

Общий доступ к ресурсам между источниками (CORS):

  • Является стандартом W3C, который позволяет серверу расслабиться в политике одного и того же источника.
  • Не является функцией безопасности, CORS смягчает безопасность. API не безопаснее, разрешая CORS. Дополнительные сведения см. в статье о работе CORS.
  • Позволяет серверу явно разрешать некоторые запросы между источниками при отклонении других.
  • Является более безопасным и более гибким, чем более ранние методы, такие как JSONP.

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

Тот же источник

Два URL-адреса имеют одинаковый источник, если они имеют одинаковые схемы, узлы и порты (RFC 6454).

Эти два URL-адреса имеют одинаковый источник:

  • https://example.com/foo.html
  • https://example.com/bar.html

Эти URL-адреса имеют разные источники, отличные от предыдущих двух URL-адресов:

  • https://example.net: другой домен
  • https://contoso.example.com/foo.html: другой поддомен
  • http://example.com/foo.html: другая схема
  • https://example.com:9000/foo.html: другой порт

Включение CORS

Существует три способа включения CORS:

Использование атрибута [EnableCors] с именованной политикой обеспечивает лучший контроль в ограничении конечных точек, поддерживающих CORS.

Предупреждение

UseCors должен вызываться в правильном порядке. Дополнительные сведения см. в порядке по промежуточного слоя. Например, UseCors необходимо вызвать перед UseResponseCaching использованием UseResponseCaching.

Каждый подход подробно описан в следующих разделах.

CORS с именованной политикой и ПО промежуточного слоя

ПО промежуточного слоя CORS обрабатывает запросы между источниками. Следующий код применяет политику CORS ко всем конечным точкам приложения с указанными источниками:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Предыдущий код:

  • Задает для _myAllowSpecificOriginsимени политики значение . Имя политики является произвольным.
  • UseCors Вызывает метод расширения и задает _myAllowSpecificOrigins политику CORS. UseCors добавляет ПО промежуточного слоя CORS. UseCors Вызов должен быть помещен после UseRouting, но до UseAuthorization. Дополнительные сведения см. в порядке по промежуточного слоя.
  • Вызовы AddCors с лямбда-выражением. Лямбда принимает CorsPolicyBuilder объект. Параметры конфигурации, например WithOrigins, описаны далее в этой статье.
  • _myAllowSpecificOrigins Включает политику CORS для всех конечных точек контроллера. См. маршрутизацию конечных точек, чтобы применить политику CORS к определенным конечным точкам.
  • При использовании по промежуточного слоя кэширования ответов вызовите UseCors раньше UseResponseCaching.

При маршрутизации конечных точек ПО промежуточного слоя CORS необходимо настроить для выполнения между вызовами UseRouting и UseEndpoints.

Вызов AddCors метода добавляет службы CORS в контейнер службы приложения:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Дополнительные сведения см. в разделе "Параметры политики CORS" в этом документе.

Методы CorsPolicyBuilder можно связать, как показано в следующем коде:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Примечание. Указанный URL-адрес не должен содержать косую черту (/). Если URL-адрес завершается /, сравнение возвращается false и заголовок не возвращается.

Порядок UseCors и UseStaticFiles

Как правило, UseStaticFiles вызывается раньше UseCors. Приложения, использующие JavaScript для получения статических файлов, должны вызываться перед UseStaticFilesвызовомUseCors.

CORS с политикой по умолчанию и ПО промежуточного слоя

Следующий выделенный код включает политику CORS по умолчанию:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Приведенный выше код применяет политику CORS по умолчанию ко всем конечным точкам контроллера.

Включение CORS с маршрутизацией конечных точек

При маршрутизации конечных точек CORS можно включить на основе каждой конечной точки с помощью RequireCors набора методов расширения:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

В предыдущем коде:

  • app.UseCors включает ПО промежуточного слоя CORS. Так как политика по умолчанию не настроена, app.UseCors() только не включает CORS.
  • Конечные /echo точки и конечные точки контроллера разрешают запросы между источниками с помощью указанной политики.
  • Конечные /echo2 точки и Razor страницы не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.RequireCors

Ознакомьтесь с атрибутом Test CORS с атрибутом [EnableCors] и методом RequireCors для инструкций по тестированию кода, аналогичного приведенному выше.

Включение CORS с атрибутами

Включение CORS с атрибутом [EnableCors] и применение именованной политики только к тем конечным точкам, которым требуется CORS, обеспечивает лучший контроль.

Атрибут [EnableCors] предоставляет альтернативу применению CORS глобально. Атрибут [EnableCors] включает CORS для выбранных конечных точек, а не для всех конечных точек:

  • [EnableCors] указывает политику по умолчанию.
  • [EnableCors("{Policy String}")] указывает именованную политику.

Атрибут [EnableCors] можно применить к:

  • Razor Страница PageModel
  • Контроллер
  • Метод действия контроллера

Различные политики можно применять к контроллерам, моделям страниц или методам действий с атрибутом [EnableCors] . [EnableCors] Если атрибут применяется к контроллеру, модели страницы или методу действия, а CORS включен в ПО промежуточного слоя, применяются обе политики. Рекомендуется объединять политики. Используйте[EnableCors] атрибут или ПО промежуточного слоя, а не в одном приложении.

Следующий код применяет другую политику к каждому методу:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Следующий код создает две политики CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Для лучшего контроля ограничения запросов CORS:

  • Используется [EnableCors("MyPolicy")] с именованной политикой.
  • Не определяйте политику по умолчанию.
  • Не используйте маршрутизацию конечных точек.

Код в следующем разделе соответствует предыдущему списку.

Отключение CORS

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.

Следующий код определяет политику "MyPolicy"CORS:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Следующий код отключает CORS для GetValues2 действия:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Предыдущий код:

Сведения о тестировании предыдущего кода см. в разделе Test CORS .

Параметры политики CORS

В этом разделе описаны различные параметры, которые можно задать в политике CORS:

AddPolicy вызывается в Program.cs. Для некоторых вариантов может быть полезно сначала ознакомиться с разделом о работе CORS.

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

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

Примечание.

Указание AllowAnyOrigin и AllowCredentials является небезопасной конфигурацией и может привести к подделке межсайтовых запросов. Служба CORS возвращает недопустимый ответ CORS, если приложение настроено с использованием обоих методов.

AllowAnyOrigin влияет на предварительные Access-Control-Allow-Origin запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

SetIsOriginAllowedToAllowWildcardSubdomains: задает IsOriginAllowed свойство политики, которая позволяет источникам сопоставлять настроенный домен подстановочных знаков при оценке допустимости источника.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Установка разрешенных методов HTTP

AllowAnyMethod:

  • Разрешает любой метод HTTP:
  • Влияет на предварительные Access-Control-Allow-Methods запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

Настройка заголовков разрешенных запросов

Чтобы разрешить отправку определенных заголовков в запросе CORS, называется заголовками запросов автора, вызовом WithHeaders и указанием разрешенных заголовков:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader влияет на предварительные запросы и заголовок Access-Control-Request-Headers . Дополнительные сведения см. в разделе "Предварительные запросы ".

Политика ПО промежуточного слоя CORS соответствует определенным заголовкам, указанным WithHeaders только в том случае, если заголовки, отправленные точно Access-Control-Request-Headers соответствуют заголовкам, указанным в .WithHeaders

Например, рассмотрим приложение, настроенное следующим образом:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

ПО промежуточного слоя CORS отклоняет предварительный запрос со следующим заголовком запроса, так как Content-Language (HeaderNames.ContentLanguage) не указан вWithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

Приложение возвращает ответ 200 OK , но не отправляет заголовки CORS обратно. Поэтому браузер не пытается выполнить запрос между источниками.

Настройка заголовков ответов, предоставляемых

По умолчанию браузер не предоставляет все заголовки ответа приложению. Дополнительные сведения см. в разделе "Общий доступ к ресурсам между источниками W3C" (терминология): простой заголовок ответа.

Заголовки ответов, доступные по умолчанию:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Учетные данные в запросах между источниками

Учетные данные требуют специальной обработки в запросе CORS. По умолчанию браузер не отправляет учетные данные с запросом между источниками. Учетные данные включают файлы cookie и схемы проверки подлинности HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentials true.

Использование XMLHttpRequest напрямую:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Использование jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Использование API получения:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Сервер должен разрешить учетные данные. Чтобы разрешить учетные данные между источниками, вызовите AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Http-ответ содержит заголовок, который сообщает браузеру Access-Control-Allow-Credentials , что сервер разрешает учетные данные для запроса между источниками.

Если браузер отправляет учетные данные, но ответ не содержит допустимый Access-Control-Allow-Credentials заголовок, браузер не предоставляет ответ приложению, а запрос между источниками завершается ошибкой.

Разрешение учетных данных между источниками — это риск безопасности. Веб-сайт в другом домене может отправлять учетные данные пользователя, выполнившего вход, в приложение от имени пользователя без знаний пользователя.

Спецификация CORS также указывает, что установка источников "*" (всех источников) недопустима, если заголовок Access-Control-Allow-Credentials присутствует.

Предварительные запросы

Для некоторых запросов CORS браузер отправляет дополнительный запрос OPTIONS перед выполнением фактического запроса. Этот запрос называется предварительным запросом. Браузер может пропустить предварительный запрос, если выполняются все следующие условия:

  • Метод запроса — GET, HEAD или POST.
  • Приложение не задает заголовки запросов, отличные от Accept, Accept-Language, , Content-LanguageContent-Typeили Last-Event-ID.
  • Заголовок Content-Type , если задано, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeader XMLHttpRequest объекта. Спецификация CORS вызывает эти заголовки для создания запросов. Правило не применяется к заголовкам, которые браузер может задать, например User-Agent, Hostили Content-Length.

Примечание.

В этой статье содержатся URL-адреса, созданные путем развертывания примера кода на двух веб-сайтах https://cors3.azurewebsites.net Azure и https://cors.azurewebsites.net.

Ниже приведен пример ответа, аналогичного запросу предварительной проверки, сделанному на кнопке [Put test] в разделе Test CORS этого документа.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Предварительный запрос использует метод HTTP OPTIONS . Он может включать следующие заголовки:

Если запрос на предварительную 200 OK проверку запрещен, приложение возвращает ответ, но не задает заголовки CORS. Поэтому браузер не пытается выполнить запрос между источниками. Пример отклоненного предварительного запроса см. в разделе test CORS этого документа.

С помощью средств F12 консольное приложение отображает ошибку, аналогичную одной из следующих, в зависимости от браузера:

  • Firefox: запрос между источниками заблокирован: та же политика источника запрещает чтение удаленного ресурса по адресу https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Причина: запрос CORS не выполнен). Подробнее
  • На основе Chromium: доступ к получению по адресу "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" из источникаhttps://cors3.azurewebsites.net "" был заблокирован политикой CORS: ответ на предварительный запрос не передает проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Чтобы разрешить определенные заголовки, вызовите WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Браузеры не согласованы в том, как они заданы Access-Control-Request-Headers. Если одно из следующих вариантов:

  • Заголовки имеют значение, отличное от других "*"
  • AllowAnyHeader вызывается: включите по крайней мере Accept, Content-Typeи Origin, а также все пользовательские заголовки, которые требуется поддерживать.

Код автоматического предварительного запроса

При применении политики CORS:

  • Глобально путем вызова app.UseCors Program.cs.
  • Использование атрибута [EnableCors] .

ASP.NET Core отвечает на запрос параметров предварительной проверки.

В разделе Test CORS этого документа показано это поведение.

Атрибут [HttpOptions] для предварительных запросов

Если CORS включена с соответствующей политикой, ASP.NET Core обычно реагирует на запросы предварительной проверки CORS автоматически.

Следующий код использует атрибут [HttpOptions] для создания конечных точек для запросов OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Сведения о тестировании предыдущего кода см. в разделе Test CORS с атрибутом [EnableCors] и методОм RequireCors.

Установка времени окончания срока действия предварительного срока действия

Заголовок Access-Control-Max-Age указывает, сколько времени можно кэшировать ответ на предварительный запрос. Чтобы задать этот заголовок, вызовите SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Включение CORS в конечной точке

Как работает CORS

В этом разделе описывается, что происходит в запросе CORS на уровне HTTP-сообщений.

  • CORS не является функцией безопасности. CORS — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника.
    • Например, злоумышленник может использовать межсайтовые скрипты (XSS) на вашем сайте и выполнить межсайтовый запрос на свой сайт с поддержкой CORS для кражи информации.
  • API не безопаснее, разрешая CORS.
    • Это до клиента (браузера) для принудительного применения CORS. Сервер выполняет запрос и возвращает ответ, он является клиентом, который возвращает ошибку и блокирует ответ. Например, любой из следующих средств отобразит ответ сервера:
  • Это способ для сервера, чтобы разрешить браузерам выполнять запрос XHR или API получения, который в противном случае будет запрещен.
    • Браузеры без CORS не могут выполнять запросы между источниками. Перед CORS JSONP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

Спецификация CORS представила несколько новых заголовков HTTP, которые позволяют выполнять запросы между источниками. Если браузер поддерживает CORS, он автоматически задает эти заголовки для запросов между источниками. Пользовательский код JavaScript не требуется для включения CORS.

Ниже приведен пример запроса между источниками из кнопки "Тест значений " в https://cors1.azurewebsites.net/api/values. Заголовок Origin :

  • Предоставляет домен сайта, выполняющего запрос.
  • Требуется и должен отличаться от узла.

Общие заголовки

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Заголовки ответа

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

В OPTIONS запросах сервер задает заголовок заголовков Access-Control-Allow-Origin: {allowed origin} ответа в ответе. Например, в примере кода Delete [EnableCors] запрос кнопки OPTIONS содержит следующие заголовки:

Общие заголовки

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Заголовки ответа

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

В предыдущих заголовках ответа сервер задает заголовок Access-Control-Allow-Origin в ответе. Значение https://cors1.azurewebsites.net этого заголовка соответствует заголовку Origin из запроса.

Если AllowAnyOrigin вызывается, Access-Control-Allow-Origin: *возвращается подстановочное значение. AllowAnyOrigin разрешает любой источник.

Если ответ не включает Access-Control-Allow-Origin заголовок, запрос между источниками завершается ошибкой. В частности, браузер запрещает запрос. Даже если сервер возвращает успешный ответ, браузер не делает ответ доступным для клиентского приложения.

Перенаправление HTTP на HTTPS вызывает ERR_INVALID_REDIRECT в предварительном запросе CORS

Запросы к конечной точке с использованием HTTP, перенаправленных на HTTPS, сбоем UseHttpsRedirection ERR_INVALID_REDIRECT on the CORS preflight request.

Проекты API могут отклонять HTTP-запросы, а не использовать UseHttpsRedirection для перенаправления запросов на HTTPS.

CORS в IIS

При развертывании в IIS CORS необходимо запустить перед проверкой подлинности Windows, если сервер не настроен для предоставления анонимного доступа. Для поддержки этого сценария необходимо установить и настроить модуль IIS CORS для приложения.

Тестирование CORS

Пример скачивания содержит код для тестирования CORS. См. раздел Практическое руководство. Скачивание файла. Примером является проект API с Razor добавленными страницами:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Предупреждение

WithOrigins("https://localhost:<port>"); следует использовать только для тестирования примера приложения, аналогичного примеру кода скачивания.

ValuesController Ниже приведены конечные точки для тестирования:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает информацию о маршруте.

Протестируйте предыдущий пример кода с помощью одного из следующих подходов:

  • Запустите пример с dotnet run использованием URL-адреса https://localhost:5001по умолчанию.
  • Запустите пример из Visual Studio с портом 44398 для URL-адреса https://localhost:44398.

Использование браузера с инструментами F12:

  • Нажмите кнопку "Значения" и просмотрите заголовки на вкладке "Сеть".

  • Нажмите кнопку "Тест PUT ". Сведения о отображении запроса OPTIONS см. в разделе "Запросы ПАРАМЕТРОВ ". Тест PUT создает два запроса, предварительный запрос OPTIONS и запрос PUT.

  • Нажмите кнопку GetValues2 [DisableCors] , чтобы активировать неудачный запрос CORS. Как упоминалось в документе, ответ возвращает 200 успешно, но запрос CORS не выполняется. Перейдите на вкладку "Консоль", чтобы увидеть ошибку CORS. В зависимости от браузера отображается ошибка, аналогичная следующему:

    Доступ к получению 'https://cors1.azurewebsites.net/api/values/GetValues2' из источника 'https://cors3.azurewebsites.net' заблокирован политикой CORS: в запрошенном ресурсе отсутствует заголовок Access-Control-Allow-Origin. Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Конечные точки с поддержкой CORS можно протестировать с помощью средства, например curl или Fiddler. При использовании средства источник запроса, указанного Origin заголовком, должен отличаться от узла, получающего запрос. Если запрос не является кросс-источником на основе значения заголовка Origin :

  • Для обработки запроса не требуется ПО промежуточного слоя CORS.
  • Заголовки CORS не возвращаются в ответе.

Следующая команда используется curl для выдачи запроса OPTIONS с информацией:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Тестирование CORS с помощью атрибута [EnableCors] и метода RequireCors

Рассмотрим следующий код, который использует маршрутизацию конечных точек для включения CORS на основе каждой конечной точки с помощью RequireCors:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Обратите внимание, что только /echo конечная точка используется RequireCors для разрешения запросов между источниками с помощью указанной политики. Приведенные ниже контроллеры позволяют CORS использовать атрибут [EnableCors].

TodoItems1Controller Ниже приведены конечные точки для тестирования:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Кнопки Delete [EnableCors] и GET [EnableCors] успешно выполнены, так как конечные точки имеют [EnableCors] и отвечают на предварительные запросы. Сбой других конечных точек. Кнопка GET завершается ошибкой, так как JavaScript отправляет:

 headers: {
      "Content-Type": "x-custom-header"
 },

TodoItems2Controller Ниже приведены аналогичные конечные точки, но содержит явный код для реагирования на запросы OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Приведенный выше код можно проверить, развернув пример в Azure. В раскрывающемся списке контроллера выберите "Предварительная проверка " и " Задать контроллер". Все вызовы CORS к TodoItems2Controller конечным точкам успешно выполнены.

Дополнительные ресурсы

Авторы: Рик Андерсон (Rick Anderson) и Кирк Ларкин (Kirk Larkin)

В этой статье показано, как включить CORS в приложении ASP.NET Core.

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

Общий доступ к ресурсам между источниками (CORS):

  • Является стандартом W3C, который позволяет серверу расслабиться в политике одного и того же источника.
  • Не является функцией безопасности, CORS смягчает безопасность. API не безопаснее, разрешая CORS. Дополнительные сведения см. в статье о работе CORS.
  • Позволяет серверу явно разрешать некоторые запросы между источниками при отклонении других.
  • Является более безопасным и более гибким, чем более ранние методы, такие как JSONP.

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

Тот же источник

Два URL-адреса имеют одинаковый источник, если они имеют одинаковые схемы, узлы и порты (RFC 6454).

Эти два URL-адреса имеют одинаковый источник:

  • https://example.com/foo.html
  • https://example.com/bar.html

Эти URL-адреса имеют разные источники, отличные от предыдущих двух URL-адресов:

  • https://example.net: другой домен
  • https://www.example.com/foo.html: другой поддомен
  • http://example.com/foo.html: другая схема
  • https://example.com:9000/foo.html: другой порт

Включение CORS

Существует три способа включения CORS:

Использование атрибута [EnableCors] с именованной политикой обеспечивает лучший контроль в ограничении конечных точек, поддерживающих CORS.

Предупреждение

UseCors должен вызываться в правильном порядке. Дополнительные сведения см. в порядке по промежуточного слоя. Например, UseCors необходимо вызвать перед UseResponseCaching использованием UseResponseCaching.

Каждый подход подробно описан в следующих разделах.

CORS с именованной политикой и ПО промежуточного слоя

ПО промежуточного слоя CORS обрабатывает запросы между источниками. Следующий код применяет политику CORS ко всем конечным точкам приложения с указанными источниками:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Предыдущий код:

  • Задает для _myAllowSpecificOriginsимени политики значение . Имя политики является произвольным.
  • UseCors Вызывает метод расширения и задает _myAllowSpecificOrigins политику CORS. UseCors добавляет ПО промежуточного слоя CORS. UseCors Вызов должен быть помещен после UseRouting, но до UseAuthorization. Дополнительные сведения см. в порядке по промежуточного слоя.
  • Вызовы AddCors с лямбда-выражением. Лямбда принимает CorsPolicyBuilder объект. Параметры конфигурации, например WithOrigins, описаны далее в этой статье.
  • _myAllowSpecificOrigins Включает политику CORS для всех конечных точек контроллера. См. маршрутизацию конечных точек, чтобы применить политику CORS к определенным конечным точкам.
  • При использовании по промежуточного слоя кэширования ответов вызовите UseCors раньше UseResponseCaching.

При маршрутизации конечных точек ПО промежуточного слоя CORS необходимо настроить для выполнения между вызовами UseRouting и UseEndpoints.

Вызов AddCors метода добавляет службы CORS в контейнер службы приложения:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Дополнительные сведения см. в разделе "Параметры политики CORS" в этом документе.

Методы CorsPolicyBuilder можно связать, как показано в следующем коде:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Примечание. Указанный URL-адрес не должен содержать косую черту (/). Если URL-адрес завершается /, сравнение возвращается false и заголовок не возвращается.

Предупреждение

UseCors должно быть помещено после UseRouting и до UseAuthorization. Это позволяет убедиться, что заголовки CORS включены в ответ как для авторизованных, так и для несанкционированных вызовов.

Порядок UseCors и UseStaticFiles

Как правило, UseStaticFiles вызывается раньше UseCors. Приложения, использующие JavaScript для получения статических файлов, должны вызываться перед UseStaticFilesвызовомUseCors.

CORS с политикой по умолчанию и ПО промежуточного слоя

Следующий выделенный код включает политику CORS по умолчанию:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Приведенный выше код применяет политику CORS по умолчанию ко всем конечным точкам контроллера.

Включение CORS с маршрутизацией конечных точек

При маршрутизации конечных точек CORS можно включить на основе каждой конечной точки с помощью RequireCors набора методов расширения:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

В предыдущем коде:

  • app.UseCors включает ПО промежуточного слоя CORS. Так как политика по умолчанию не настроена, app.UseCors() только не включает CORS.
  • Конечные /echo точки и конечные точки контроллера разрешают запросы между источниками с помощью указанной политики.
  • Конечные /echo2 точки и Razor страницы не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.RequireCors

В ASP.NET Core 7.0 [EnableCors] атрибут должен передать параметр или предупреждение ASP0023 создается из неоднозначного соответствия маршрута. ASP.NET Core 8.0 и более поздних версий ASP0023 не создает предупреждение.

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Ознакомьтесь с атрибутом Test CORS с атрибутом [EnableCors] и методом RequireCors для инструкций по тестированию кода, аналогичного приведенному выше.

Включение CORS с атрибутами

Включение CORS с атрибутом [EnableCors] и применение именованной политики только к тем конечным точкам, которым требуется CORS, обеспечивает лучший контроль.

Атрибут [EnableCors] предоставляет альтернативу применению CORS глобально. Атрибут [EnableCors] включает CORS для выбранных конечных точек, а не для всех конечных точек:

  • [EnableCors] указывает политику по умолчанию.
  • [EnableCors("{Policy String}")] указывает именованную политику.

Атрибут [EnableCors] можно применить к:

  • Razor Страница PageModel
  • Контроллер
  • Метод действия контроллера

Различные политики можно применять к контроллерам, моделям страниц или методам действий с атрибутом [EnableCors] . [EnableCors] Если атрибут применяется к контроллеру, модели страницы или методу действия, а CORS включен в ПО промежуточного слоя, применяются обе политики. Рекомендуется объединять политики. Используйте[EnableCors] атрибут или ПО промежуточного слоя, а не в одном приложении.

Следующий код применяет другую политику к каждому методу:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Следующий код создает две политики CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Для лучшего контроля ограничения запросов CORS:

  • Используется [EnableCors("MyPolicy")] с именованной политикой.
  • Не определяйте политику по умолчанию.
  • Не используйте маршрутизацию конечных точек.

Код в следующем разделе соответствует предыдущему списку.

Отключение CORS

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.

Следующий код определяет политику "MyPolicy"CORS:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints => {
    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Следующий код отключает CORS для GetValues2 действия:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Предыдущий код:

Сведения о тестировании предыдущего кода см. в разделе Test CORS .

Параметры политики CORS

В этом разделе описаны различные параметры, которые можно задать в политике CORS:

AddPolicy вызывается в Program.cs. Для некоторых вариантов может быть полезно сначала ознакомиться с разделом о работе CORS.

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

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

Примечание.

Указание AllowAnyOrigin и AllowCredentials является небезопасной конфигурацией и может привести к подделке межсайтовых запросов. Служба CORS возвращает недопустимый ответ CORS, если приложение настроено с использованием обоих методов.

AllowAnyOrigin влияет на предварительные Access-Control-Allow-Origin запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

SetIsOriginAllowedToAllowWildcardSubdomains: задает IsOriginAllowed свойство политики, которая позволяет источникам сопоставлять настроенный домен подстановочных знаков при оценке допустимости источника.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Установка разрешенных методов HTTP

AllowAnyMethod:

  • Разрешает любой метод HTTP:
  • Влияет на предварительные Access-Control-Allow-Methods запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

Настройка заголовков разрешенных запросов

Чтобы разрешить отправку определенных заголовков в запросе CORS, называется заголовками запросов автора, вызовом WithHeaders и указанием разрешенных заголовков:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader влияет на предварительные запросы и заголовок Access-Control-Request-Headers . Дополнительные сведения см. в разделе "Предварительные запросы ".

Политика ПО промежуточного слоя CORS соответствует определенным заголовкам, указанным WithHeaders только в том случае, если заголовки, отправленные точно Access-Control-Request-Headers соответствуют заголовкам, указанным в .WithHeaders

Например, рассмотрим приложение, настроенное следующим образом:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

ПО промежуточного слоя CORS отклоняет предварительный запрос со следующим заголовком запроса, так как Content-Language (HeaderNames.ContentLanguage) не указан вWithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

Приложение возвращает ответ 200 OK , но не отправляет заголовки CORS обратно. Поэтому браузер не пытается выполнить запрос между источниками.

Настройка заголовков ответов, предоставляемых

По умолчанию браузер не предоставляет все заголовки ответа приложению. Дополнительные сведения см. в разделе "Общий доступ к ресурсам между источниками W3C" (терминология): простой заголовок ответа.

Заголовки ответов, доступные по умолчанию:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Учетные данные в запросах между источниками

Учетные данные требуют специальной обработки в запросе CORS. По умолчанию браузер не отправляет учетные данные с запросом между источниками. Учетные данные включают файлы cookie и схемы проверки подлинности HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentials true.

Использование XMLHttpRequest напрямую:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Использование jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Использование API получения:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Сервер должен разрешить учетные данные. Чтобы разрешить учетные данные между источниками, вызовите AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Http-ответ содержит заголовок, который сообщает браузеру Access-Control-Allow-Credentials , что сервер разрешает учетные данные для запроса между источниками.

Если браузер отправляет учетные данные, но ответ не содержит допустимый Access-Control-Allow-Credentials заголовок, браузер не предоставляет ответ приложению, а запрос между источниками завершается ошибкой.

Разрешение учетных данных между источниками — это риск безопасности. Веб-сайт в другом домене может отправлять учетные данные пользователя, выполнившего вход, в приложение от имени пользователя без знаний пользователя.

Спецификация CORS также указывает, что установка источников "*" (всех источников) недопустима, если заголовок Access-Control-Allow-Credentials присутствует.

Предварительные запросы

Для некоторых запросов CORS браузер отправляет дополнительный запрос OPTIONS перед выполнением фактического запроса. Этот запрос называется предварительным запросом. Браузер может пропустить предварительный запрос, если выполняются все следующие условия:

  • Метод запроса — GET, HEAD или POST.
  • Приложение не задает заголовки запросов, отличные от Accept, Accept-Language, , Content-LanguageContent-Typeили Last-Event-ID.
  • Заголовок Content-Type , если задано, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeader XMLHttpRequest объекта. Спецификация CORS вызывает эти заголовки для создания запросов. Правило не применяется к заголовкам, которые браузер может задать, например User-Agent, Hostили Content-Length.

Ниже приведен пример ответа, аналогичного запросу предварительной проверки, сделанному на кнопке [Put test] в разделе Test CORS этого документа.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Предварительный запрос использует метод HTTP OPTIONS . Он может включать следующие заголовки:

Если запрос на предварительную 200 OK проверку запрещен, приложение возвращает ответ, но не задает заголовки CORS. Поэтому браузер не пытается выполнить запрос между источниками. Пример отклоненного предварительного запроса см. в разделе test CORS этого документа.

С помощью средств F12 консольное приложение отображает ошибку, аналогичную одной из следующих, в зависимости от браузера:

  • Firefox: запрос между источниками заблокирован: та же политика источника запрещает чтение удаленного ресурса по адресу https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Причина: запрос CORS не выполнен). Подробнее
  • На основе Chromium: доступ к получению по адресу "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" из источникаhttps://cors3.azurewebsites.net "" был заблокирован политикой CORS: ответ на предварительный запрос не передает проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Чтобы разрешить определенные заголовки, вызовите WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Браузеры не согласованы в том, как они заданы Access-Control-Request-Headers. Если одно из следующих вариантов:

  • Заголовки имеют значение, отличное от других "*"
  • AllowAnyHeader вызывается: включите по крайней мере Accept, Content-Typeи Origin, а также все пользовательские заголовки, которые требуется поддерживать.

Код автоматического предварительного запроса

При применении политики CORS:

  • Глобально путем вызова app.UseCors Program.cs.
  • Использование атрибута [EnableCors] .

ASP.NET Core отвечает на запрос параметров предварительной проверки.

В разделе Test CORS этого документа показано это поведение.

Атрибут [HttpOptions] для предварительных запросов

Если CORS включена с соответствующей политикой, ASP.NET Core обычно реагирует на запросы предварительной проверки CORS автоматически.

Следующий код использует атрибут [HttpOptions] для создания конечных точек для запросов OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Сведения о тестировании предыдущего кода см. в разделе Test CORS с атрибутом [EnableCors] и методОм RequireCors.

Установка времени окончания срока действия предварительного срока действия

Заголовок Access-Control-Max-Age указывает, сколько времени можно кэшировать ответ на предварительный запрос. Чтобы задать этот заголовок, вызовите SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Включение CORS в конечной точке

Как работает CORS

В этом разделе описывается, что происходит в запросе CORS на уровне HTTP-сообщений.

  • CORS не является функцией безопасности. CORS — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника.
    • Например, злоумышленник может использовать межсайтовые скрипты (XSS) на вашем сайте и выполнить межсайтовый запрос на свой сайт с поддержкой CORS для кражи информации.
  • API не безопаснее, разрешая CORS.
    • Это до клиента (браузера) для принудительного применения CORS. Сервер выполняет запрос и возвращает ответ, он является клиентом, который возвращает ошибку и блокирует ответ. Например, любой из следующих средств отобразит ответ сервера:
  • Это способ для сервера, чтобы разрешить браузерам выполнять запрос XHR или API получения, который в противном случае будет запрещен.
    • Браузеры без CORS не могут выполнять запросы между источниками. Перед CORS JSONP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

Спецификация CORS представила несколько новых заголовков HTTP, которые позволяют выполнять запросы между источниками. Если браузер поддерживает CORS, он автоматически задает эти заголовки для запросов между источниками. Пользовательский код JavaScript не требуется для включения CORS.

Нажмите кнопку PUT теста в развернутом примере. Заголовок Origin :

  • Предоставляет домен сайта, выполняющего запрос.
  • Требуется и должен отличаться от узла.

Общие заголовки

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Заголовки ответа

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

В OPTIONS запросах сервер задает заголовок заголовков Access-Control-Allow-Origin: {allowed origin} ответа в ответе. Например, в примере кода Delete [EnableCors] запрос кнопки OPTIONS содержит следующие заголовки:

Общие заголовки

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Заголовки ответа

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

В предыдущих заголовках ответа сервер задает заголовок Access-Control-Allow-Origin в ответе. Значение https://cors1.azurewebsites.net этого заголовка соответствует заголовку Origin из запроса.

Если AllowAnyOrigin вызывается, Access-Control-Allow-Origin: *возвращается подстановочное значение. AllowAnyOrigin разрешает любой источник.

Если ответ не включает Access-Control-Allow-Origin заголовок, запрос между источниками завершается ошибкой. В частности, браузер запрещает запрос. Даже если сервер возвращает успешный ответ, браузер не делает ответ доступным для клиентского приложения.

Перенаправление HTTP на HTTPS вызывает ERR_INVALID_REDIRECT в предварительном запросе CORS

Запросы к конечной точке с использованием HTTP, перенаправленных на HTTPS, сбоем UseHttpsRedirection ERR_INVALID_REDIRECT on the CORS preflight request.

Проекты API могут отклонять HTTP-запросы, а не использовать UseHttpsRedirection для перенаправления запросов на HTTPS.

CORS в IIS

При развертывании в IIS CORS необходимо запустить перед проверкой подлинности Windows, если сервер не настроен для предоставления анонимного доступа. Для поддержки этого сценария необходимо установить и настроить модуль IIS CORS для приложения.

Тестирование CORS

Пример скачивания содержит код для тестирования CORS. См. раздел Практическое руководство. Скачивание файла. Примером является проект API с Razor добавленными страницами:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Предупреждение

WithOrigins("https://localhost:<port>"); следует использовать только для тестирования примера приложения, аналогичного примеру кода скачивания.

ValuesController Ниже приведены конечные точки для тестирования:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает информацию о маршруте.

Протестируйте предыдущий пример кода с помощью одного из следующих подходов:

  • Запустите пример с dotnet run использованием URL-адреса https://localhost:5001по умолчанию.
  • Запустите пример из Visual Studio с портом 44398 для URL-адреса https://localhost:44398.

Использование браузера с инструментами F12:

  • Нажмите кнопку "Значения" и просмотрите заголовки на вкладке "Сеть".

  • Нажмите кнопку "Тест PUT ". Сведения о отображении запроса OPTIONS см. в разделе "Запросы ПАРАМЕТРОВ ". Тест PUT создает два запроса, предварительный запрос OPTIONS и запрос PUT.

  • Нажмите кнопку GetValues2 [DisableCors] , чтобы активировать неудачный запрос CORS. Как упоминалось в документе, ответ возвращает 200 успешно, но запрос CORS не выполняется. Перейдите на вкладку "Консоль", чтобы увидеть ошибку CORS. В зависимости от браузера отображается ошибка, аналогичная следующему:

    Доступ к получению 'https://cors1.azurewebsites.net/api/values/GetValues2' из источника 'https://cors3.azurewebsites.net' заблокирован политикой CORS: в запрошенном ресурсе отсутствует заголовок Access-Control-Allow-Origin. Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Конечные точки с поддержкой CORS можно протестировать с помощью средства, например curl или Fiddler. При использовании средства источник запроса, указанного Origin заголовком, должен отличаться от узла, получающего запрос. Если запрос не является кросс-источником на основе значения заголовка Origin :

  • Для обработки запроса не требуется ПО промежуточного слоя CORS.
  • Заголовки CORS не возвращаются в ответе.

Следующая команда используется curl для выдачи запроса OPTIONS с информацией:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Тестирование CORS с помощью атрибута [EnableCors] и метода RequireCors

Рассмотрим следующий код, который использует маршрутизацию конечных точек для включения CORS на основе каждой конечной точки с помощью RequireCors:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                    "http://www.contoso.com",
                    "https://cors1.azurewebsites.net",
                    "https://cors3.azurewebsites.net",
                    "https://localhost:44398",
                    "https://localhost:5001")
                .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors("MyPolicy");

    endpoints.MapControllers();
    endpoints.MapRazorPages();
});

app.Run();

Обратите внимание, что только /echo конечная точка используется RequireCors для разрешения запросов между источниками с помощью указанной политики. Приведенные ниже контроллеры позволяют CORS использовать атрибут [EnableCors].

TodoItems1Controller Ниже приведены конечные точки для тестирования:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase 
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id) {
        if (id < 1) {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors("MyPolicy")]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Кнопки Delete [EnableCors] и GET [EnableCors] успешно выполнены, так как конечные точки имеют [EnableCors] и отвечают на предварительные запросы. Сбой других конечных точек. Кнопка GET завершается ошибкой, так как JavaScript отправляет:

 headers: {
      "Content-Type": "x-custom-header"
 },

TodoItems2Controller Ниже приведены аналогичные конечные точки, но содержит явный код для реагирования на запросы OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided.
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // [EnableCors] //  Warning ASP0023 Route '{id}' conflicts with another action route.
    //                  An HTTP request that matches multiple routes results in an ambiguous
    //                  match error.
    [EnableCors("MyPolicy")] // Required for this path.
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors("MyPolicy")]  // Required for this path.
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Приведенный выше код можно проверить, развернув пример, чтобы Azure.In раскрывающемся списке контроллера , выберите предварительный просмотр и установите контроллер. Все вызовы CORS к TodoItems2Controller конечным точкам успешно выполнены.

Дополнительные ресурсы

Авторы: Рик Андерсон (Rick Anderson) и Кирк Ларкин (Kirk Larkin)

В этой статье показано, как включить CORS в приложении ASP.NET Core.

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

Общий доступ к ресурсам между источниками (CORS):

  • Является стандартом W3C, который позволяет серверу расслабиться в политике одного и того же источника.
  • Не является функцией безопасности, CORS смягчает безопасность. API не безопаснее, разрешая CORS. Дополнительные сведения см. в статье о работе CORS.
  • Позволяет серверу явно разрешать некоторые запросы между источниками при отклонении других.
  • Является более безопасным и более гибким, чем более ранние методы, такие как JSONP.

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

Тот же источник

Два URL-адреса имеют одинаковый источник, если они имеют одинаковые схемы, узлы и порты (RFC 6454).

Эти два URL-адреса имеют одинаковый источник:

  • https://example.com/foo.html
  • https://example.com/bar.html

Эти URL-адреса имеют разные источники, отличные от предыдущих двух URL-адресов:

  • https://example.net: другой домен
  • https://www.example.com/foo.html: другой поддомен
  • http://example.com/foo.html: другая схема
  • https://example.com:9000/foo.html: другой порт

Включение CORS

Существует три способа включения CORS:

Использование атрибута [EnableCors] с именованной политикой обеспечивает лучший контроль в ограничении конечных точек, поддерживающих CORS.

Предупреждение

UseCors должен вызываться в правильном порядке. Дополнительные сведения см. в порядке по промежуточного слоя. Например, UseCors необходимо вызвать перед UseResponseCaching использованием UseResponseCaching.

Каждый подход подробно описан в следующих разделах.

CORS с именованной политикой и ПО промежуточного слоя

ПО промежуточного слоя CORS обрабатывает запросы между источниками. Следующий код применяет политику CORS ко всем конечным точкам приложения с указанными источниками:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Предыдущий код:

  • Задает для _myAllowSpecificOriginsимени политики значение . Имя политики является произвольным.
  • UseCors Вызывает метод расширения и задает _myAllowSpecificOrigins политику CORS. UseCors добавляет ПО промежуточного слоя CORS. UseCors Вызов должен быть помещен после UseRouting, но до UseAuthorization. Дополнительные сведения см. в порядке по промежуточного слоя.
  • Вызовы AddCors с лямбда-выражением. Лямбда принимает CorsPolicyBuilder объект. Параметры конфигурации, например WithOrigins, описаны далее в этой статье.
  • _myAllowSpecificOrigins Включает политику CORS для всех конечных точек контроллера. См. маршрутизацию конечных точек, чтобы применить политику CORS к определенным конечным точкам.
  • При использовании по промежуточного слоя кэширования ответов вызовите UseCors раньше UseResponseCaching.

При маршрутизации конечных точек ПО промежуточного слоя CORS необходимо настроить для выполнения между вызовами UseRouting и UseEndpoints.

Вызов AddCors метода добавляет службы CORS в контейнер службы приложения:

var  MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy  =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

// services.AddResponseCaching();

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Дополнительные сведения см. в разделе "Параметры политики CORS" в этом документе.

Методы CorsPolicyBuilder можно связать, как показано в следующем коде:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
});

builder.Services.AddControllers();

var app = builder.Build();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors(MyAllowSpecificOrigins);

app.UseAuthorization();

app.MapControllers();

app.Run();

Примечание. Указанный URL-адрес не должен содержать косую черту (/). Если URL-адрес завершается /, сравнение возвращается false и заголовок не возвращается.

Предупреждение

UseCors должно быть помещено после UseRouting и до UseAuthorization. Это позволяет убедиться, что заголовки CORS включены в ответ как для авторизованных, так и для несанкционированных вызовов.

Порядок UseCors и UseStaticFiles

Как правило, UseStaticFiles вызывается раньше UseCors. Приложения, использующие JavaScript для получения статических файлов, должны вызываться перед UseStaticFilesвызовомUseCors.

CORS с политикой по умолчанию и ПО промежуточного слоя

Следующий выделенный код включает политику CORS по умолчанию:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Приведенный выше код применяет политику CORS по умолчанию ко всем конечным точкам контроллера.

Включение CORS с маршрутизацией конечных точек

Включение CORS на основе каждой конечной точки не RequireCors поддерживает автоматические предварительные запросы. Дополнительные сведения см. в этой проблеме GitHub и тестировании CORS с маршрутизацией конечных точек и [HttpOptions].

При маршрутизации конечных точек CORS можно включить на основе каждой конечной точки с помощью RequireCors набора методов расширения:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
                      policy =>
                      {
                          policy.WithOrigins("http://example.com",
                                              "http://www.contoso.com");
                      });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/echo",
        context => context.Response.WriteAsync("echo"))
        .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapControllers()
             .RequireCors(MyAllowSpecificOrigins);

    endpoints.MapGet("/echo2",
        context => context.Response.WriteAsync("echo2"));

    endpoints.MapRazorPages();
});

app.Run();

В предыдущем коде:

  • app.UseCors включает ПО промежуточного слоя CORS. Так как политика по умолчанию не настроена, app.UseCors() только не включает CORS.
  • Конечные /echo точки и конечные точки контроллера разрешают запросы между источниками с помощью указанной политики.
  • Конечные /echo2 точки и Razor страницы не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.RequireCors

Ознакомьтесь с инструкциями по тестированию CORS с маршрутизацией конечных точек и [HttpOptions] , как показано выше.

Включение CORS с атрибутами

Включение CORS с атрибутом [EnableCors] и применение именованной политики только к тем конечным точкам, которым требуется CORS, обеспечивает лучший контроль.

Атрибут [EnableCors] предоставляет альтернативу применению CORS глобально. Атрибут [EnableCors] включает CORS для выбранных конечных точек, а не для всех конечных точек:

  • [EnableCors] указывает политику по умолчанию.
  • [EnableCors("{Policy String}")] указывает именованную политику.

Атрибут [EnableCors] можно применить к:

  • Razor Страница PageModel
  • Контроллер
  • Метод действия контроллера

Различные политики можно применять к контроллерам, моделям страниц или методам действий с атрибутом [EnableCors] . [EnableCors] Если атрибут применяется к контроллеру, модели страницы или методу действия, а CORS включен в ПО промежуточного слоя, применяются обе политики. Рекомендуется объединять политики. Используйте[EnableCors] атрибут или ПО промежуточного слоя, а не в одном приложении.

Следующий код применяет другую политику к каждому методу:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Следующий код создает две политики CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("Policy1",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com");
        });

    options.AddPolicy("AnotherPolicy",
        policy =>
        {
            policy.WithOrigins("http://www.contoso.com")
                                .AllowAnyHeader()
                                .AllowAnyMethod();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();

app.Run();

Для лучшего контроля ограничения запросов CORS:

  • Используется [EnableCors("MyPolicy")] с именованной политикой.
  • Не определяйте политику по умолчанию.
  • Не используйте маршрутизацию конечных точек.

Код в следующем разделе соответствует предыдущему списку.

Отключение CORS

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.

Следующий код определяет политику "MyPolicy"CORS:

var MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com",
                                "http://www.contoso.com")
                    .WithMethods("PUT", "DELETE", "GET");
        });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Следующий код отключает CORS для GetValues2 действия:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Предыдущий код:

Сведения о тестировании предыдущего кода см. в разделе Test CORS .

Параметры политики CORS

В этом разделе описаны различные параметры, которые можно задать в политике CORS:

AddPolicy вызывается в Program.cs. Для некоторых вариантов может быть полезно сначала ознакомиться с разделом о работе CORS.

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

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

Примечание.

Указание AllowAnyOrigin и AllowCredentials является небезопасной конфигурацией и может привести к подделке межсайтовых запросов. Служба CORS возвращает недопустимый ответ CORS, если приложение настроено с использованием обоих методов.

AllowAnyOrigin влияет на предварительные Access-Control-Allow-Origin запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

SetIsOriginAllowedToAllowWildcardSubdomains: задает IsOriginAllowed свойство политики, которая позволяет источникам сопоставлять настроенный домен подстановочных знаков при оценке допустимости источника.

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                .SetIsOriginAllowedToAllowWildcardSubdomains();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Установка разрешенных методов HTTP

AllowAnyMethod:

  • Разрешает любой метод HTTP:
  • Влияет на предварительные Access-Control-Allow-Methods запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

Настройка заголовков разрешенных запросов

Чтобы разрешить отправку определенных заголовков в запросе CORS, называется заголовками запросов автора, вызовом WithHeaders и указанием разрешенных заголовков:

using Microsoft.Net.Http.Headers;

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
       policy =>
       {
           policy.WithOrigins("http://example.com")
                  .WithHeaders(HeaderNames.ContentType, "x-custom-header");
       });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

var MyAllowSpecificOrigins = "_MyAllowSubdomainPolicy";

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: MyAllowSpecificOrigins,
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

AllowAnyHeader влияет на предварительные запросы и заголовок Access-Control-Request-Headers . Дополнительные сведения см. в разделе "Предварительные запросы ".

Политика ПО промежуточного слоя CORS соответствует определенным заголовкам, указанным WithHeaders только в том случае, если заголовки, отправленные точно Access-Control-Request-Headers соответствуют заголовкам, указанным в .WithHeaders

Например, рассмотрим приложение, настроенное следующим образом:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

ПО промежуточного слоя CORS отклоняет предварительный запрос со следующим заголовком запроса, так как Content-Language (HeaderNames.ContentLanguage) не указан вWithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

Приложение возвращает ответ 200 OK , но не отправляет заголовки CORS обратно. Поэтому браузер не пытается выполнить запрос между источниками.

Настройка заголовков ответов, предоставляемых

По умолчанию браузер не предоставляет все заголовки ответа приложению. Дополнительные сведения см. в разделе "Общий доступ к ресурсам между источниками W3C" (терминология): простой заголовок ответа.

Заголовки ответов, доступные по умолчанию:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

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

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyExposeResponseHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .WithExposedHeaders("x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Учетные данные в запросах между источниками

Учетные данные требуют специальной обработки в запросе CORS. По умолчанию браузер не отправляет учетные данные с запросом между источниками. Учетные данные включают файлы cookie и схемы проверки подлинности HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentials true.

Использование XMLHttpRequest напрямую:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Использование jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Использование API получения:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Сервер должен разрешить учетные данные. Чтобы разрешить учетные данные между источниками, вызовите AllowCredentials:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyMyAllowCredentialsPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .AllowCredentials();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Http-ответ содержит заголовок, который сообщает браузеру Access-Control-Allow-Credentials , что сервер разрешает учетные данные для запроса между источниками.

Если браузер отправляет учетные данные, но ответ не содержит допустимый Access-Control-Allow-Credentials заголовок, браузер не предоставляет ответ приложению, а запрос между источниками завершается ошибкой.

Разрешение учетных данных между источниками — это риск безопасности. Веб-сайт в другом домене может отправлять учетные данные пользователя, выполнившего вход, в приложение от имени пользователя без знаний пользователя.

Спецификация CORS также указывает, что установка источников "*" (всех источников) недопустима, если заголовок Access-Control-Allow-Credentials присутствует.

Предварительные запросы

Для некоторых запросов CORS браузер отправляет дополнительный запрос OPTIONS перед выполнением фактического запроса. Этот запрос называется предварительным запросом. Браузер может пропустить предварительный запрос, если выполняются все следующие условия:

  • Метод запроса — GET, HEAD или POST.
  • Приложение не задает заголовки запросов, отличные от Accept, Accept-Language, , Content-LanguageContent-Typeили Last-Event-ID.
  • Заголовок Content-Type , если задано, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeader XMLHttpRequest объекта. Спецификация CORS вызывает эти заголовки для создания запросов. Правило не применяется к заголовкам, которые браузер может задать, например User-Agent, Hostили Content-Length.

Ниже приведен пример ответа, аналогичного запросу предварительной проверки, сделанному на кнопке [Put test] в разделе Test CORS этого документа.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Предварительный запрос использует метод HTTP OPTIONS . Он может включать следующие заголовки:

Если запрос на предварительную 200 OK проверку запрещен, приложение возвращает ответ, но не задает заголовки CORS. Поэтому браузер не пытается выполнить запрос между источниками. Пример отклоненного предварительного запроса см. в разделе test CORS этого документа.

С помощью средств F12 консольное приложение отображает ошибку, аналогичную одной из следующих, в зависимости от браузера:

  • Firefox: запрос между источниками заблокирован: та же политика источника запрещает чтение удаленного ресурса по адресу https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Причина: запрос CORS не выполнен). Подробнее
  • На основе Chromium: доступ к получению по адресу "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" из источникаhttps://cors3.azurewebsites.net "" был заблокирован политикой CORS: ответ на предварительный запрос не передает проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Чтобы разрешить определенные заголовки, вызовите WithHeaders:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowHeadersPolicy",
        policy =>
        {
        policy.WithOrigins("http://example.com")
                   .WithHeaders(HeaderNames.ContentType, "x-custom-header");
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

using Microsoft.Net.Http.Headers;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MyAllowAllHeadersPolicy",
        policy =>
        {
            policy.WithOrigins("https://*.example.com")
                   .AllowAnyHeader();
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Браузеры не согласованы в том, как они заданы Access-Control-Request-Headers. Если одно из следующих вариантов:

  • Заголовки имеют значение, отличное от других "*"
  • AllowAnyHeader вызывается: включите по крайней мере Accept, Content-Typeи Origin, а также все пользовательские заголовки, которые требуется поддерживать.

Код автоматического предварительного запроса

При применении политики CORS:

  • Глобально путем вызова app.UseCors Program.cs.
  • Использование атрибута [EnableCors] .

ASP.NET Core отвечает на запрос параметров предварительной проверки.

Включение CORS на основе каждой конечной точки с использованием RequireCors в настоящее время не поддерживает автоматические предварительные запросы.

В разделе Test CORS этого документа показано это поведение.

Атрибут [HttpOptions] для предварительных запросов

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

Следующий код использует атрибут [HttpOptions] для создания конечных точек для запросов OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Инструкции по тестированию предыдущего кода см. в разделе Test CORS с маршрутизацией конечных точек и [HttpOptions] .

Установка времени окончания срока действия предварительного срока действия

Заголовок Access-Control-Max-Age указывает, сколько времени можно кэшировать ответ на предварительный запрос. Чтобы задать этот заголовок, вызовите SetPreflightMaxAge:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy("MySetPreflightExpirationPolicy",
        policy =>
        {
            policy.WithOrigins("http://example.com")
                   .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
        });
});

builder.Services.AddControllers();

var app = builder.Build();

Как работает CORS

В этом разделе описывается, что происходит в запросе CORS на уровне HTTP-сообщений.

  • CORS не является функцией безопасности. CORS — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника.
    • Например, злоумышленник может использовать межсайтовые скрипты (XSS) на вашем сайте и выполнить межсайтовый запрос на свой сайт с поддержкой CORS для кражи информации.
  • API не безопаснее, разрешая CORS.
    • Это до клиента (браузера) для принудительного применения CORS. Сервер выполняет запрос и возвращает ответ, он является клиентом, который возвращает ошибку и блокирует ответ. Например, любой из следующих средств отобразит ответ сервера:
  • Это способ для сервера, чтобы разрешить браузерам выполнять запрос XHR или API получения, который в противном случае будет запрещен.
    • Браузеры без CORS не могут выполнять запросы между источниками. Перед CORS JSONP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

Спецификация CORS представила несколько новых заголовков HTTP, которые позволяют выполнять запросы между источниками. Если браузер поддерживает CORS, он автоматически задает эти заголовки для запросов между источниками. Пользовательский код JavaScript не требуется для включения CORS.

Ниже приведен пример запроса между источниками из кнопки "Тест значений " в https://cors1.azurewebsites.net/api/values. Заголовок Origin :

  • Предоставляет домен сайта, выполняющего запрос.
  • Требуется и должен отличаться от узла.

Общие заголовки

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Заголовки ответа

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

В OPTIONS запросах сервер задает заголовок заголовков Access-Control-Allow-Origin: {allowed origin} ответа в ответе. Например, развернутый пример запроса кнопки OPTIONS Delete [EnableCors] содержит следующие заголовки:

Общие заголовки

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Заголовки ответа

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

В предыдущих заголовках ответа сервер задает заголовок Access-Control-Allow-Origin в ответе. Значение https://cors1.azurewebsites.net этого заголовка соответствует заголовку Origin из запроса.

Если AllowAnyOrigin вызывается, Access-Control-Allow-Origin: *возвращается подстановочное значение. AllowAnyOrigin разрешает любой источник.

Если ответ не включает Access-Control-Allow-Origin заголовок, запрос между источниками завершается ошибкой. В частности, браузер запрещает запрос. Даже если сервер возвращает успешный ответ, браузер не делает ответ доступным для клиентского приложения.

Перенаправление HTTP на HTTPS вызывает ERR_INVALID_REDIRECT в предварительном запросе CORS

Запросы к конечной точке с использованием HTTP, перенаправленных на HTTPS, сбоем UseHttpsRedirection ERR_INVALID_REDIRECT on the CORS preflight request.

Проекты API могут отклонять HTTP-запросы, а не использовать UseHttpsRedirection для перенаправления запросов на HTTPS.

Отображение запросов OPTIONS

По умолчанию браузеры Chrome и Edge не отображают запросы OPTIONS на сетевой вкладке инструментов F12. Чтобы отобразить запросы OPTIONS в этих браузерах, выполните следующие действия.

  • chrome://flags/#out-of-blink-cors или edge://flags/#out-of-blink-cors
  • отключите флаг.
  • перезапуск.

Firefox отображает запросы OPTIONS по умолчанию.

CORS в IIS

При развертывании в IIS CORS необходимо запустить перед проверкой подлинности Windows, если сервер не настроен для предоставления анонимного доступа. Для поддержки этого сценария необходимо установить и настроить модуль IIS CORS для приложения.

Тестирование CORS

Пример скачивания содержит код для тестирования CORS. См. раздел Практическое руководство. Скачивание файла. Примером является проект API с Razor добавленными страницами:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

Предупреждение

WithOrigins("https://localhost:<port>"); следует использовать только для тестирования примера приложения, аналогичного примеру кода скачивания.

ValuesController Ниже приведены конечные точки для тестирования:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает информацию о маршруте.

Протестируйте предыдущий пример кода с помощью одного из следующих подходов:

  • Запустите пример с dotnet run использованием URL-адреса https://localhost:5001по умолчанию.
  • Запустите пример из Visual Studio с портом 44398 для URL-адреса https://localhost:44398.

Использование браузера с инструментами F12:

  • Нажмите кнопку "Значения" и просмотрите заголовки на вкладке "Сеть".

  • Нажмите кнопку "Тест PUT ". Сведения о отображении запроса OPTIONS см. в разделе "Запросы ПАРАМЕТРОВ ". Тест PUT создает два запроса, предварительный запрос OPTIONS и запрос PUT.

  • Нажмите кнопку GetValues2 [DisableCors] , чтобы активировать неудачный запрос CORS. Как упоминалось в документе, ответ возвращает 200 успешно, но запрос CORS не выполняется. Перейдите на вкладку "Консоль", чтобы увидеть ошибку CORS. В зависимости от браузера отображается ошибка, аналогичная следующему:

    Доступ к получению 'https://cors1.azurewebsites.net/api/values/GetValues2' из источника 'https://cors3.azurewebsites.net' заблокирован политикой CORS: в запрошенном ресурсе отсутствует заголовок Access-Control-Allow-Origin. Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Конечные точки с поддержкой CORS можно протестировать с помощью средства, например curl или Fiddler. При использовании средства источник запроса, указанного Origin заголовком, должен отличаться от узла, получающего запрос. Если запрос не является кросс-источником на основе значения заголовка Origin :

  • Для обработки запроса не требуется ПО промежуточного слоя CORS.
  • Заголовки CORS не возвращаются в ответе.

Следующая команда используется curl для выдачи запроса OPTIONS с информацией:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Тестирование CORS с помощью маршрутизации конечных точек и [HttpOptions]

Включение CORS на основе каждой конечной точки с использованием RequireCors в настоящее время не поддерживает автоматические предварительные запросы. Рассмотрим следующий код, использующий маршрутизацию конечных точек для включения CORS:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCors(options =>
{
    options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
});

builder.Services.AddControllers();
builder.Services.AddRazorPages();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseCors();

app.UseAuthorization();

app.MapControllers();
app.MapRazorPages();

app.Run();

TodoItems1Controller Ниже приведены конечные точки для тестирования:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Проверьте предыдущий код на тестовой странице (https://cors1.azurewebsites.net/test?number=1) развернутого примера.

Кнопки Delete [EnableCors] и GET [EnableCors] успешно выполнены, так как конечные точки имеют [EnableCors] и отвечают на предварительные запросы. Сбой других конечных точек. Кнопка GET завершается ошибкой, так как JavaScript отправляет:

 headers: {
      "Content-Type": "x-custom-header"
 },

TodoItems2Controller Ниже приведены аналогичные конечные точки, но содержит явный код для реагирования на запросы OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Приведенный выше код можно проверить, развернув пример, чтобы Azure.In раскрывающемся списке контроллера , выберите предварительный просмотр и установите контроллер. Все вызовы CORS к TodoItems2Controller конечным точкам успешно выполнены.

Дополнительные ресурсы

Авторы: Рик Андерсон (Rick Anderson) и Кирк Ларкин (Kirk Larkin)

В этой статье показано, как включить CORS в приложении ASP.NET Core.

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

Общий доступ к ресурсам между источниками (CORS):

  • Является стандартом W3C, который позволяет серверу расслабиться в политике одного и того же источника.
  • Не является функцией безопасности, CORS смягчает безопасность. API не безопаснее, разрешая CORS. Дополнительные сведения см. в статье о работе CORS.
  • Позволяет серверу явно разрешать некоторые запросы между источниками при отклонении других.
  • Является более безопасным и более гибким, чем более ранние методы, такие как JSONP.

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

Тот же источник

Два URL-адреса имеют одинаковый источник, если они имеют одинаковые схемы, узлы и порты (RFC 6454).

Эти два URL-адреса имеют одинаковый источник:

  • https://example.com/foo.html
  • https://example.com/bar.html

Эти URL-адреса имеют разные источники, отличные от предыдущих двух URL-адресов:

  • https://example.net: другой домен
  • https://www.example.com/foo.html: другой поддомен
  • http://example.com/foo.html: другая схема
  • https://example.com:9000/foo.html: другой порт

Включение CORS

Существует три способа включения CORS:

Использование атрибута [EnableCors] с именованной политикой обеспечивает лучший контроль в ограничении конечных точек, поддерживающих CORS.

Предупреждение

UseCors должен вызываться в правильном порядке. Дополнительные сведения см. в порядке по промежуточного слоя. Например, UseCors необходимо вызвать перед UseResponseCaching использованием UseResponseCaching.

Каждый подход подробно описан в следующих разделах.

CORS с именованной политикой и ПО промежуточного слоя

ПО промежуточного слоя CORS обрабатывает запросы между источниками. Следующий код применяет политику CORS ко всем конечным точкам приложения с указанными источниками:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors(MyAllowSpecificOrigins);

        // app.UseResponseCaching();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Предыдущий код:

  • Задает для _myAllowSpecificOriginsимени политики значение . Имя политики является произвольным.
  • UseCors Вызывает метод расширения и задает _myAllowSpecificOrigins политику CORS. UseCors добавляет ПО промежуточного слоя CORS. UseCors Вызов должен быть помещен после UseRouting, но до UseAuthorization. Дополнительные сведения см. в порядке по промежуточного слоя.
  • Вызовы AddCors с лямбда-выражением. Лямбда принимает CorsPolicyBuilder объект. Параметры конфигурации, например WithOrigins, описаны далее в этой статье.
  • _myAllowSpecificOrigins Включает политику CORS для всех конечных точек контроллера. См. маршрутизацию конечных точек, чтобы применить политику CORS к определенным конечным точкам.
  • При использовании по промежуточного слоя кэширования ответов вызовите UseCors раньше UseResponseCaching.

При маршрутизации конечных точек ПО промежуточного слоя CORS необходимо настроить для выполнения между вызовами UseRouting и UseEndpoints.

Ознакомьтесь с инструкциями по тестированию кода, аналогичного приведенному выше.

Вызов AddCors метода добавляет службы CORS в контейнер службы приложения:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        // services.AddResponseCaching();
        services.AddControllers();
    }

Дополнительные сведения см. в разделе "Параметры политики CORS" в этом документе.

Методы CorsPolicyBuilder можно связать, как показано в следующем коде:

public void ConfigureServices(IServiceCollection services)
{
    services.AddCors(options =>
    {
        options.AddPolicy(MyAllowSpecificOrigins,
                          policy =>
                          {
                              policy.WithOrigins("http://example.com",
                                                  "http://www.contoso.com")
                                                  .AllowAnyHeader()
                                                  .AllowAnyMethod();
                          });
    });

    services.AddControllers();
}

Примечание. Указанный URL-адрес не должен содержать косую черту (/). Если URL-адрес завершается /, сравнение возвращается false и заголовок не возвращается.

CORS с политикой по умолчанию и ПО промежуточного слоя

Следующий выделенный код включает политику CORS по умолчанию:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddDefaultPolicy(
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Приведенный выше код применяет политику CORS по умолчанию ко всем конечным точкам контроллера.

Включение CORS с маршрутизацией конечных точек

Включение CORS на основе каждой конечной точки не RequireCors поддерживает автоматические предварительные запросы. Дополнительные сведения см. в этой проблеме GitHub и тестировании CORS с маршрутизацией конечных точек и [HttpOptions].

При маршрутизации конечных точек CORS можно включить на основе каждой конечной точки с помощью RequireCors набора методов расширения:

public class Startup
{
    readonly string MyAllowSpecificOrigins = "_myAllowSpecificOrigins";

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyAllowSpecificOrigins,
                              policy =>
                              {
                                  policy.WithOrigins("http://example.com",
                                                      "http://www.contoso.com");
                              });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapGet("/echo",
                context => context.Response.WriteAsync("echo"))
                .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapControllers()
                     .RequireCors(MyAllowSpecificOrigins);

            endpoints.MapGet("/echo2",
                context => context.Response.WriteAsync("echo2"));

            endpoints.MapRazorPages();
        });
    }
}

В предыдущем коде:

  • app.UseCors включает ПО промежуточного слоя CORS. Так как политика по умолчанию не настроена, app.UseCors() только не включает CORS.
  • Конечные /echo точки и конечные точки контроллера разрешают запросы между источниками с помощью указанной политики.
  • Конечные /echo2 точки и Razor страницы не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.RequireCors

Ознакомьтесь с инструкциями по тестированию CORS с маршрутизацией конечных точек и [HttpOptions] , как показано выше.

Включение CORS с атрибутами

Включение CORS с атрибутом [EnableCors] и применение именованной политики только к тем конечным точкам, которым требуется CORS, обеспечивает лучший контроль.

Атрибут [EnableCors] предоставляет альтернативу применению CORS глобально. Атрибут [EnableCors] включает CORS для выбранных конечных точек, а не для всех конечных точек:

  • [EnableCors] указывает политику по умолчанию.
  • [EnableCors("{Policy String}")] указывает именованную политику.

Атрибут [EnableCors] можно применить к:

  • Razor Страница PageModel
  • Контроллер
  • Метод действия контроллера

Различные политики можно применять к контроллерам, моделям страниц или методам действий с атрибутом [EnableCors] . [EnableCors] Если атрибут применяется к контроллеру, модели страницы или методу действия, а CORS включен в ПО промежуточного слоя, применяются обе политики. Рекомендуется объединять политики. Используйте[EnableCors] атрибут или ПО промежуточного слоя, а не в одном приложении.

Следующий код применяет другую политику к каждому методу:

[Route("api/[controller]")]
[ApiController]
public class WidgetController : ControllerBase
{
    // GET api/values
    [EnableCors("AnotherPolicy")]
    [HttpGet]
    public ActionResult<IEnumerable<string>> Get()
    {
        return new string[] { "green widget", "red widget" };
    }

    // GET api/values/5
    [EnableCors("Policy1")]
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return id switch
        {
            1 => "green widget",
            2 => "red widget",
            _ => NotFound(),
        };
    }
}

Следующий код создает две политики CORS:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("Policy1",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com");
                });

            options.AddPolicy("AnotherPolicy",
                policy =>
                {
                    policy.WithOrigins("http://www.contoso.com")
                                        .AllowAnyHeader()
                                        .AllowAnyMethod();
                });
        });

        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

Для лучшего контроля ограничения запросов CORS:

  • Используется [EnableCors("MyPolicy")] с именованной политикой.
  • Не определяйте политику по умолчанию.
  • Не используйте маршрутизацию конечных точек.

Код в следующем разделе соответствует предыдущему списку.

Ознакомьтесь с инструкциями по тестированию кода, аналогичного приведенному выше.

Отключение CORS

Атрибут [DisableCors] не отключает CORS, включенную маршрутизацией конечных точек.

Следующий код определяет политику "MyPolicy"CORS:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

Следующий код отключает CORS для GetValues2 действия:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

Предыдущий код:

Сведения о тестировании предыдущего кода см. в разделе Test CORS .

Параметры политики CORS

В этом разделе описаны различные параметры, которые можно задать в политике CORS:

AddPolicy вызывается в Startup.ConfigureServices. Для некоторых вариантов может быть полезно сначала ознакомиться с разделом о работе CORS.

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

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

Примечание.

Указание AllowAnyOrigin и AllowCredentials является небезопасной конфигурацией и может привести к подделке межсайтовых запросов. Служба CORS возвращает недопустимый ответ CORS, если приложение настроено с использованием обоих методов.

AllowAnyOrigin влияет на предварительные Access-Control-Allow-Origin запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

SetIsOriginAllowedToAllowWildcardSubdomains: задает IsOriginAllowed свойство политики, которая позволяет источникам сопоставлять настроенный домен подстановочных знаков при оценке допустимости источника.

options.AddPolicy("MyAllowSubdomainPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
            .SetIsOriginAllowedToAllowWildcardSubdomains();
    });

Установка разрешенных методов HTTP

AllowAnyMethod:

  • Разрешает любой метод HTTP:
  • Влияет на предварительные Access-Control-Allow-Methods запросы и заголовок. Дополнительные сведения см. в разделе "Предварительные запросы ".

Настройка заголовков разрешенных запросов

Чтобы разрешить отправку определенных заголовков в запросе CORS, называется заголовками запросов автора, вызовом WithHeaders и указанием разрешенных заголовков:

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

AllowAnyHeader влияет на предварительные запросы и заголовок Access-Control-Request-Headers . Дополнительные сведения см. в разделе "Предварительные запросы ".

Политика ПО промежуточного слоя CORS соответствует определенным заголовкам, указанным WithHeaders только в том случае, если заголовки, отправленные точно Access-Control-Request-Headers соответствуют заголовкам, указанным в .WithHeaders

Например, рассмотрим приложение, настроенное следующим образом:

app.UseCors(policy => policy.WithHeaders(HeaderNames.CacheControl));

ПО промежуточного слоя CORS отклоняет предварительный запрос со следующим заголовком запроса, так как Content-Language (HeaderNames.ContentLanguage) не указан вWithHeaders:

Access-Control-Request-Headers: Cache-Control, Content-Language

Приложение возвращает ответ 200 OK , но не отправляет заголовки CORS обратно. Поэтому браузер не пытается выполнить запрос между источниками.

Настройка заголовков ответов, предоставляемых

По умолчанию браузер не предоставляет все заголовки ответа приложению. Дополнительные сведения см. в разделе "Общий доступ к ресурсам между источниками W3C" (терминология): простой заголовок ответа.

Заголовки ответов, доступные по умолчанию:

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

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

options.AddPolicy("MyExposeResponseHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .WithExposedHeaders("x-custom-header");
    });

Учетные данные в запросах между источниками

Учетные данные требуют специальной обработки в запросе CORS. По умолчанию браузер не отправляет учетные данные с запросом между источниками. Учетные данные включают файлы cookie и схемы проверки подлинности HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentials true.

Использование XMLHttpRequest напрямую:

var xhr = new XMLHttpRequest();
xhr.open('get', 'https://www.example.com/api/test');
xhr.withCredentials = true;

Использование jQuery:

$.ajax({
  type: 'get',
  url: 'https://www.example.com/api/test',
  xhrFields: {
    withCredentials: true
  }
});

Использование API получения:

fetch('https://www.example.com/api/test', {
    credentials: 'include'
});

Сервер должен разрешить учетные данные. Чтобы разрешить учетные данные между источниками, вызовите AllowCredentials:

options.AddPolicy("MyMyAllowCredentialsPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .AllowCredentials();
    });

Http-ответ содержит заголовок, который сообщает браузеру Access-Control-Allow-Credentials , что сервер разрешает учетные данные для запроса между источниками.

Если браузер отправляет учетные данные, но ответ не содержит допустимый Access-Control-Allow-Credentials заголовок, браузер не предоставляет ответ приложению, а запрос между источниками завершается ошибкой.

Разрешение учетных данных между источниками — это риск безопасности. Веб-сайт в другом домене может отправлять учетные данные пользователя, выполнившего вход, в приложение от имени пользователя без знаний пользователя.

Спецификация CORS также указывает, что установка источников "*" (всех источников) недопустима, если заголовок Access-Control-Allow-Credentials присутствует.

Предварительные запросы

Для некоторых запросов CORS браузер отправляет дополнительный запрос OPTIONS перед выполнением фактического запроса. Этот запрос называется предварительным запросом. Браузер может пропустить предварительный запрос, если выполняются все следующие условия:

  • Метод запроса — GET, HEAD или POST.
  • Приложение не задает заголовки запросов, отличные от Accept, Accept-Language, , Content-LanguageContent-Typeили Last-Event-ID.
  • Заголовок Content-Type , если задано, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeader XMLHttpRequest объекта. Спецификация CORS вызывает эти заголовки для создания запросов. Правило не применяется к заголовкам, которые браузер может задать, например User-Agent, Hostили Content-Length.

Ниже приведен пример ответа, аналогичного запросу предварительной проверки, сделанному на кнопке [Put test] в разделе Test CORS этого документа.

General:
Request URL: https://cors3.azurewebsites.net/api/values/5
Request Method: OPTIONS
Status Code: 204 No Content

Response Headers:
Access-Control-Allow-Methods: PUT,DELETE,GET
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f8...8;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Vary: Origin

Request Headers:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Method: PUT
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

Предварительный запрос использует метод HTTP OPTIONS . Он может включать следующие заголовки:

Если запрос на предварительную 200 OK проверку запрещен, приложение возвращает ответ, но не задает заголовки CORS. Поэтому браузер не пытается выполнить запрос между источниками. Пример отклоненного предварительного запроса см. в разделе test CORS этого документа.

С помощью средств F12 консольное приложение отображает ошибку, аналогичную одной из следующих, в зависимости от браузера:

  • Firefox: запрос между источниками заблокирован: та же политика источника запрещает чтение удаленного ресурса по адресу https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5. (Причина: запрос CORS не выполнен). Подробнее
  • На основе Chromium: доступ к получению по адресу "https://cors1.azurewebsites.net/api/TodoItems1/MyDelete2/5" из источникаhttps://cors3.azurewebsites.net "" был заблокирован политикой CORS: ответ на предварительный запрос не передает проверку контроля доступа: в запрошенном ресурсе отсутствует заголовок "Access-Control-Allow-Origin". Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Чтобы разрешить определенные заголовки, вызовите WithHeaders:

options.AddPolicy("MyAllowHeadersPolicy",
    policy =>
    {
        // requires using Microsoft.Net.Http.Headers;
        policy.WithOrigins("http://example.com")
               .WithHeaders(HeaderNames.ContentType, "x-custom-header");
    });

Чтобы разрешить все заголовки запросов автора, вызовите AllowAnyHeader:

options.AddPolicy("MyAllowAllHeadersPolicy",
    policy =>
    {
        policy.WithOrigins("https://*.example.com")
               .AllowAnyHeader();
    });

Браузеры не согласованы в том, как они заданы Access-Control-Request-Headers. Если одно из следующих вариантов:

  • Заголовки имеют значение, отличное от других "*"
  • AllowAnyHeader вызывается: включите по крайней мере Accept, Content-Typeи Origin, а также все пользовательские заголовки, которые требуется поддерживать.

Код автоматического предварительного запроса

При применении политики CORS:

  • Глобально путем вызова app.UseCors Startup.Configure.
  • Использование атрибута [EnableCors] .

ASP.NET Core отвечает на запрос параметров предварительной проверки.

Включение CORS на основе каждой конечной точки с использованием RequireCors в настоящее время не поддерживает автоматические предварительные запросы.

В разделе Test CORS этого документа показано это поведение.

Атрибут [HttpOptions] для предварительных запросов

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

Следующий код использует атрибут [HttpOptions] для создания конечных точек для запросов OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

Инструкции по тестированию предыдущего кода см. в разделе Test CORS с маршрутизацией конечных точек и [HttpOptions] .

Установка времени окончания срока действия предварительного срока действия

Заголовок Access-Control-Max-Age указывает, сколько времени можно кэшировать ответ на предварительный запрос. Чтобы задать этот заголовок, вызовите SetPreflightMaxAge:

options.AddPolicy("MySetPreflightExpirationPolicy",
    policy =>
    {
        policy.WithOrigins("http://example.com")
               .SetPreflightMaxAge(TimeSpan.FromSeconds(2520));
    });

Как работает CORS

В этом разделе описывается, что происходит в запросе CORS на уровне HTTP-сообщений.

  • CORS не является функцией безопасности. CORS — это стандарт W3C, позволяющий серверу расслабиться в политике одного источника.
    • Например, злоумышленник может использовать межсайтовые скрипты (XSS) на вашем сайте и выполнить межсайтовый запрос на свой сайт с поддержкой CORS для кражи информации.
  • API не безопаснее, разрешая CORS.
    • Это до клиента (браузера) для принудительного применения CORS. Сервер выполняет запрос и возвращает ответ, он является клиентом, который возвращает ошибку и блокирует ответ. Например, любой из следующих средств отобразит ответ сервера:
  • Это способ для сервера, чтобы разрешить браузерам выполнять запрос XHR или API получения, который в противном случае будет запрещен.
    • Браузеры без CORS не могут выполнять запросы между источниками. Перед CORS JSONP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

Спецификация CORS представила несколько новых заголовков HTTP, которые позволяют выполнять запросы между источниками. Если браузер поддерживает CORS, он автоматически задает эти заголовки для запросов между источниками. Пользовательский код JavaScript не требуется для включения CORS.

Ниже приведен пример запроса между источниками из кнопки "Тест значений " в https://cors1.azurewebsites.net/api/values. Заголовок Origin :

  • Предоставляет домен сайта, выполняющего запрос.
  • Требуется и должен отличаться от узла.

Общие заголовки

Request URL: https://cors1.azurewebsites.net/api/values
Request Method: GET
Status Code: 200 OK

Заголовки ответа

Content-Encoding: gzip
Content-Type: text/plain; charset=utf-8
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors1.azurewebsites.net
Transfer-Encoding: chunked
Vary: Accept-Encoding
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: keep-alive
Host: cors1.azurewebsites.net
Origin: https://cors3.azurewebsites.net
Referer: https://cors3.azurewebsites.net/
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0 ...

В OPTIONS запросах сервер задает заголовок заголовков Access-Control-Allow-Origin: {allowed origin} ответа в ответе. Например, развернутый пример запроса кнопки OPTIONS Delete [EnableCors] содержит следующие заголовки:

Общие заголовки

Request URL: https://cors3.azurewebsites.net/api/TodoItems2/MyDelete2/5
Request Method: OPTIONS
Status Code: 204 No Content

Заголовки ответа

Access-Control-Allow-Headers: Content-Type,x-custom-header
Access-Control-Allow-Methods: PUT,DELETE,GET,OPTIONS
Access-Control-Allow-Origin: https://cors1.azurewebsites.net
Server: Microsoft-IIS/10.0
Set-Cookie: ARRAffinity=8f...;Path=/;HttpOnly;Domain=cors3.azurewebsites.net
Vary: Origin
X-Powered-By: ASP.NET

Заголовки запроса

Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Access-Control-Request-Headers: content-type
Access-Control-Request-Method: DELETE
Connection: keep-alive
Host: cors3.azurewebsites.net
Origin: https://cors1.azurewebsites.net
Referer: https://cors1.azurewebsites.net/test?number=2
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: cross-site
User-Agent: Mozilla/5.0

В предыдущих заголовках ответа сервер задает заголовок Access-Control-Allow-Origin в ответе. Значение https://cors1.azurewebsites.net этого заголовка соответствует заголовку Origin из запроса.

Если AllowAnyOrigin вызывается, Access-Control-Allow-Origin: *возвращается подстановочное значение. AllowAnyOrigin разрешает любой источник.

Если ответ не включает Access-Control-Allow-Origin заголовок, запрос между источниками завершается ошибкой. В частности, браузер запрещает запрос. Даже если сервер возвращает успешный ответ, браузер не делает ответ доступным для клиентского приложения.

Отображение запросов OPTIONS

По умолчанию браузеры Chrome и Edge не отображают запросы OPTIONS на сетевой вкладке инструментов F12. Чтобы отобразить запросы OPTIONS в этих браузерах, выполните следующие действия.

  • chrome://flags/#out-of-blink-cors или edge://flags/#out-of-blink-cors
  • отключите флаг.
  • перезапуск.

Firefox отображает запросы OPTIONS по умолчанию.

CORS в IIS

При развертывании в IIS CORS необходимо запустить перед проверкой подлинности Windows, если сервер не настроен для предоставления анонимного доступа. Для поддержки этого сценария необходимо установить и настроить модуль IIS CORS для приложения.

Тестирование CORS

Пример скачивания содержит код для тестирования CORS. См. раздел Практическое руководство. Скачивание файла. Примером является проект API с Razor добавленными страницами:

public class StartupTest2
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: "MyPolicy",
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                        "http://www.contoso.com",
                        "https://cors1.azurewebsites.net",
                        "https://cors3.azurewebsites.net",
                        "https://localhost:44398",
                        "https://localhost:5001")
                            .WithMethods("PUT", "DELETE", "GET");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
            endpoints.MapRazorPages();
        });
    }
}

Предупреждение

WithOrigins("https://localhost:<port>"); следует использовать только для тестирования примера приложения, аналогичного примеру кода скачивания.

ValuesController Ниже приведены конечные точки для тестирования:

[EnableCors("MyPolicy")]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values
    [HttpGet]
    public IActionResult Get() =>
        ControllerContext.MyDisplayRouteInfo();

    // GET api/values/5
    [HttpGet("{id}")]
    public IActionResult Get(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // PUT api/values/5
    [HttpPut("{id}")]
    public IActionResult Put(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);


    // GET: api/values/GetValues2
    [DisableCors]
    [HttpGet("{action}")]
    public IActionResult GetValues2() =>
        ControllerContext.MyDisplayRouteInfo();

}

MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает информацию о маршруте.

Протестируйте предыдущий пример кода с помощью одного из следующих подходов:

  • Запустите пример с dotnet run использованием URL-адреса https://localhost:5001по умолчанию.
  • Запустите пример из Visual Studio с портом 44398 для URL-адреса https://localhost:44398.

Использование браузера с инструментами F12:

  • Нажмите кнопку "Значения" и просмотрите заголовки на вкладке "Сеть".

  • Нажмите кнопку "Тест PUT ". Сведения о отображении запроса OPTIONS см. в разделе "Запросы ПАРАМЕТРОВ ". Тест PUT создает два запроса, предварительный запрос OPTIONS и запрос PUT.

  • Нажмите кнопку GetValues2 [DisableCors] , чтобы активировать неудачный запрос CORS. Как упоминалось в документе, ответ возвращает 200 успешно, но запрос CORS не выполняется. Перейдите на вкладку "Консоль", чтобы увидеть ошибку CORS. В зависимости от браузера отображается ошибка, аналогичная следующему:

    Доступ к получению 'https://cors1.azurewebsites.net/api/values/GetValues2' из источника 'https://cors3.azurewebsites.net' заблокирован политикой CORS: в запрошенном ресурсе отсутствует заголовок Access-Control-Allow-Origin. Если этот непрозрачный ответ вам подходит, задайте для режима запроса значение "no-cors", чтобы извлечь ресурс с отключенным параметром CORS.

Конечные точки с поддержкой CORS можно протестировать с помощью средства, например curl или Fiddler. При использовании средства источник запроса, указанного Origin заголовком, должен отличаться от узла, получающего запрос. Если запрос не является кросс-источником на основе значения заголовка Origin :

  • Для обработки запроса не требуется ПО промежуточного слоя CORS.
  • Заголовки CORS не возвращаются в ответе.

Следующая команда используется curl для выдачи запроса OPTIONS с информацией:

curl -X OPTIONS https://cors3.azurewebsites.net/api/TodoItems2/5 -i

Тестирование CORS с помощью маршрутизации конечных точек и [HttpOptions]

Включение CORS на основе каждой конечной точки с использованием RequireCors в настоящее время не поддерживает автоматические предварительные запросы. Рассмотрим следующий код, использующий маршрутизацию конечных точек для включения CORS:

public class StartupEndPointBugTest
{
    readonly string MyPolicy = "_myPolicy";

    // .WithHeaders(HeaderNames.ContentType, "x-custom-header")
    // forces browsers to require a preflight request with GET

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy(name: MyPolicy,
                policy =>
                {
                    policy.WithOrigins("http://example.com",
                                        "http://www.contoso.com",
                                        "https://cors1.azurewebsites.net",
                                        "https://cors3.azurewebsites.net",
                                        "https://localhost:44398",
                                        "https://localhost:5001")
                           .WithHeaders(HeaderNames.ContentType, "x-custom-header")
                           .WithMethods("PUT", "DELETE", "GET", "OPTIONS");
                });
        });

        services.AddControllers();
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseCors();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers().RequireCors(MyPolicy);
            endpoints.MapRazorPages();
        });
    }
}

TodoItems1Controller Ниже приведены конечные точки для тестирования:

[Route("api/[controller]")]
[ApiController]
public class TodoItems1Controller : ControllerBase
{
    // PUT: api/TodoItems1/5
    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return Content($"ID = {id}");
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // Delete: api/TodoItems1/5
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    // GET: api/TodoItems1
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]
    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    // Delete: api/TodoItems1/MyDelete2/5
    [EnableCors]
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Проверьте предыдущий код на тестовой странице (https://cors1.azurewebsites.net/test?number=1) развернутого примера.

Кнопки Delete [EnableCors] и GET [EnableCors] успешно выполнены, так как конечные точки имеют [EnableCors] и отвечают на предварительные запросы. Сбой других конечных точек. Кнопка GET завершается ошибкой, так как JavaScript отправляет:

 headers: {
      "Content-Type": "x-custom-header"
 },

TodoItems2Controller Ниже приведены аналогичные конечные точки, но содержит явный код для реагирования на запросы OPTIONS:

[Route("api/[controller]")]
[ApiController]
public class TodoItems2Controller : ControllerBase
{
    // OPTIONS: api/TodoItems2/5
    [HttpOptions("{id}")]
    public IActionResult PreflightRoute(int id)
    {
        return NoContent();
    }

    // OPTIONS: api/TodoItems2 
    [HttpOptions]
    public IActionResult PreflightRoute()
    {
        return NoContent();
    }

    [HttpPut("{id}")]
    public IActionResult PutTodoItem(int id)
    {
        if (id < 1)
        {
            return BadRequest();
        }

        return ControllerContext.MyDisplayRouteInfo(id);
    }

    // [EnableCors] // Not needed as OPTIONS path provided
    [HttpDelete("{id}")]
    public IActionResult MyDelete(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);

    [EnableCors]  // Rquired for this path
    [HttpGet]
    public IActionResult GetTodoItems() =>
        ControllerContext.MyDisplayRouteInfo();

    [HttpGet("{action}")]
    public IActionResult GetTodoItems2() =>
        ControllerContext.MyDisplayRouteInfo();

    [EnableCors]  // Rquired for this path
    [HttpDelete("{action}/{id}")]
    public IActionResult MyDelete2(int id) =>
        ControllerContext.MyDisplayRouteInfo(id);
}

Приведенный выше код можно проверить, развернув пример, чтобы Azure.In раскрывающемся списке контроллера , выберите предварительный просмотр и установите контроллер. Все вызовы CORS к TodoItems2Controller конечным точкам успешно выполнены.

Дополнительные ресурсы