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

Авторы: Рик Андерсон (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();

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

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

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

Вызов 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поддерживает автоматические предварительные запросы. Дополнительные сведения см. в этой проблеме 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 точки and Razor Pages не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [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:

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

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

Отключение 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();

}

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

Инструкции по тестированию предыдущего кода см. в разделе "Тестирование 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схемы проверки подлинности s и HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentialstrue.

Использование 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-Languageили Content-TypeLast-Event-ID.
  • Заголовок Content-Type , если задан, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeaderXMLHttpRequest объекта. Спецификация 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 отвечает на предварительный запрос OPTIONS.

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

В разделе "Тестирование 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);
    }

Инструкции по тестированию предыдущего кода см. в статье "Тестирование 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 или Fetch API между источниками, которые в противном случае будут запрещены.
    • Браузеры без CORS не могут выполнять запросы между источниками. До CORS JSonP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

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

Кнопка PUT теста в развернутом примере

Ниже приведен пример запроса между источниками из кнопки "Тест значений".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} ответа в ответе. Например, развернутый пример запроса кнопки OPTIONSDelete [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 сбоем UseHttpsRedirectionERR_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 и отображает информацию о маршруте.

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

  • Используйте развернутый пример приложения по адресу https://cors3.azurewebsites.net/. Скачивание примера не требуется.
  • Запустите пример с 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 или Postman. При использовании средства источник запроса, указанного заголовком 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);
}

Протестируйте предыдущий код на тестовой странице развернутого примера.

Кнопки 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);
}

Протестируйте предыдущий код на тестовой странице развернутого примера. В раскрывающемся списке контроллера выберите "Предварительный" и "Задать контроллер". Все вызовы 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();
        });
    }
}

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

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

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

Вызов 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 точки and Razor Pages не разрешают запросы между источниками, так как политика по умолчанию не указана.

Атрибут [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:

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

Инструкции по тестированию кода, аналогичные приведенному выше, см. в разделе "Тестирование CORS ".

Отключение 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();

}

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

Инструкции по тестированию предыдущего кода см. в разделе "Тестирование 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схемы проверки подлинности s и HTTP. Чтобы отправить учетные данные с запросом между источниками, клиент должен задать значение XMLHttpRequest.withCredentialstrue.

Использование 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-Languageили Content-TypeLast-Event-ID.
  • Заголовок Content-Type , если задан, имеет одно из следующих значений:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Правило заголовков запросов, заданных для клиентского запроса, применяется к заголовкам, заданным приложением путем вызова setRequestHeaderXMLHttpRequest объекта. Спецификация 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 отвечает на предварительный запрос OPTIONS.

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

В разделе "Тестирование 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);
    }

Инструкции по тестированию предыдущего кода см. в разделе "Тестирование 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 или Fetch API , который в противном случае будет запрещен.
    • Браузеры без CORS не могут выполнять запросы между источниками. До CORS JSONP использовался для обхода этого ограничения. JSONP не использует XHR, он использует <script> тег для получения ответа. Скрипты могут загружаться между источниками.

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

Кнопка теста PUT в развернутом примере

Ниже приведен пример запроса между источниками из кнопки проверки значений.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} ответа в ответе. Например, развернутый пример запроса кнопки OPTIONSDelete [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 и отображает информацию о маршруте.

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

  • Используйте развернутый пример приложения по адресу https://cors3.azurewebsites.net/. Нет необходимости скачивать пример.
  • Запустите пример с 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 или Postman. При использовании средства источник запроса, указанного заголовком 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);
}

Протестируйте предыдущий код на тестовой странице развернутого примера.

Кнопки 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);
}

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

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