Для обработки кроссдоменных запросов и работы CORS в прошлой теме код приложения выглядел следующим образом:
var builder = WebApplication.CreateBuilder(); builder.Services.AddCors(); // добавляем сервисы CORS var app = builder.Build(); // настраиваем CORS app.UseCors(builder => builder.AllowAnyOrigin()); app.Map("/", async context => await context.Response.WriteAsync("Hello METANIT.COM!")); app.Run();
В вызове app.UseCors() с помощью методов объекта CorsPolicyBuilder можно настроить конфигурацию CORS:
AllowAnyOrigin(): принимаются запросы с любого адреса
AllowAnyHeader(): принимаются запросы с любыми заголовками
AllowAnyMethod(): принимаются запросы любого типа (GET/POST)
AllowCredentials(): разрешается принимать идентификационные данные от клиента (например, куки)
WithHeaders(): принимаются только те запросы, которые используют содержат определенные заголовки
WithMethods(): принимаются запросы только определенного типа
WithOrigins(): принимаются запросы только с определенных адресов
WithExposedHeaders(): позволяет серверу отправлять на сторону клиента свои заголовки
Метод AllowAnyOrigin() позволяет установить взаимодействие с любым приложением по любому адресу. Однако подобное поведение может быть нежелательным. В этом случае мы можем ограничить круг адресов с помощью метода WithOrigins():
app.UseCors(builder => builder.WithOrigins("http://example.com", "http://google.com"));
При чем, что важно, в конце названия домена не должно быть конечного слеша.
Метод AllowAnyMethod() позволяет принимать запросы любого типа (GET/POST). Также можно настроить принятие только определенного типа запросов:
app.UseCors(builder => builder.WithOrigins("https://localhost:7027").WithMethods("GET"));
Для разрешения запросов с любыми заголовками применяется метод AllowAnyHeader(). Следует отметить, что вместе с этим методом лучше также указывать и метод AllowAnyMethod() или WithMethods() для указания типа запроса:
app.UseCors(builder => builder.WithOrigins("https://localhost:7027") .AllowAnyHeader() .AllowAnyMethod());
Если необходимо принимать запросы только с определенными заголовоками, то все требуемые заголовки надо передать в метод WithHeaders():
app.UseCors(builder => builder.WithOrigins("https://localhost:7027") .AllowAnyMethod() .WithHeaders("custom-header"));
В данном случае необходимо, чтобы клиент отправлял в запросе заголовок "custom-header". Например, отправка данного заголовка в коде javascript с помощью функции fetch:
<h2 id="result"></h2> <button id="btn" value="Запрос">Запрос</button> <script> const btn = document.getElementById("btn"); const result = document.getElementById("result"); btn.addEventListener("click", async () => { try { const response = await fetch("https://localhost:7199/", { headers: { "custom-header": "test" } }); if (response.ok) result.innerText = await response.text(); } catch (e) { result.innerText = e.message; } }); </script>
Если сервер отправляет какие-то свои заголовки, то по умолчанию клиент их не получает. Чтобы на стороне сервера указать, какие заголовки может получать клиент, следует использовать метод WithExposedHeaders():
var builder = WebApplication.CreateBuilder(); builder.Services.AddCors(); // добавляем сервисы CORS var app = builder.Build(); // настраиваем CORS app.UseCors(builder => builder.WithOrigins("https://localhost:7027") .AllowAnyMethod() .AllowAnyHeader() .WithExposedHeaders("custom-header")); app.Run(async (context) => { context.Response.Headers.Add("custom-header", "5678"); await context.Response.WriteAsync("Hello World!"); }); app.Run();
Сервер устанавливает заголовок custom-header и отправляет его клиенту. Чтобы клиент получил этот заголовок, он передается в метод WithExposedHeaders.
Затем на стороне клиента можно получить значение этого заголовка. Например, получение в коде JavaScript с помощью функции fetch:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test CORS</title> </head> <body> <h2 id="result"></h2> <button id="btn" value="Запрос">Запрос</button> <script> const btn = document.getElementById("btn"); const result = document.getElementById("result"); btn.addEventListener("click", async () => { try { const response = await fetch("https://localhost:7199/"); if (response.ok) { const headerTitle = "custom-header"; // название заголовка result.innerText = await response.text(); if (response.headers.has(headerTitle)) { // если заголовок есть console.log(response.headers.get(headerTitle)); // получаем его значение } } } catch (e) { result.innerText = e.message; } }); </script> </body> </html>
Альтернативное получение через XMLHttpRequest:
const btn = document.getElementById("btn"); const result = document.getElementById("result"); const headers = document.getElementById("headers"); const request = new XMLHttpRequest(); btn.addEventListener("click", function (e) { request.open("GET", "https://localhost:44313/"); request.onreadystatechange = reqReadyStateChange; request.send(); }); function reqReadyStateChange() { if (request.readyState == 4) { if (request.status == 200){ result.innerText = request.responseText; // получаем заголовок headers.innerText = request.getResponseHeader("custom-header"); } } }
По умолчанию браузер не посылает никаких идентификационных данных. Подобные данные включают куки, а также данные HTTP-аутентификации. Для отправки идентификационных данных в кроссдоменном запросе на стороне клиента у объекта XMLHttpRequest необходимо установить свойство withCredentials равным true.
const request = new XMLHttpRequest(); request.open("GET", "https://localhost:44313/"); request.withCredentials = true;
Для получения данных на стороне сервера применяется метод AllowCredentials(). Этот метод устанавливает заголовок Access-Control-Allow-Credentials
,
который говорит браузеру, что сервер разрешает отправку идентификационных данных. При этом данный метод не может использоваться с методом AllowAllOrigin, то
есть обязательно нужно указать набор адресов, с которыми будет взаимодействовать сервер. Например:
var builder = WebApplication.CreateBuilder(); builder.Services.AddCors(); // добавляем сервисы CORS var app = builder.Build(); // настраиваем CORS app.UseCors(builder => builder.WithOrigins("https://localhost:7027") .AllowCredentials()); app.Run(async (context) => { var login = context.Request.Cookies["login"]; // получаем отправленные куки await context.Response.WriteAsync($"Hello {login}!"); }); app.Run();
При отправке запроса с помощью функции fetch ей необходимо передать опцию credentials со значением include
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Test CORS</title> </head> <body> <h2 id="result"></h2> <button id="btn" value="Запрос">Запрос</button> <script> const btn = document.getElementById("btn"); const result = document.getElementById("result"); document.cookie = "login=tom32"; // куки, которые будут отправляться btn.addEventListener("click", async () => { try { const response = await fetch("https://localhost:7199/", { credentials: "include"}); if (response.ok) result.innerText = await response.text(); } catch (e) { result.innerText = e.message; } }); </script> </body> </html>
Альтернативный вариант с помощью XMLHttpRequest:
const btn = document.getElementById("btn"); const request = new XMLHttpRequest(); document.cookie = "login=tom32;"; // куки, которые будут отправляться btn.addEventListener("click", function () { request.open("GET", "https://localhost:44313/"); request.onreadystatechange = reqReadyStateChange; request.withCredentials = true; // устанавливаем отправку request.send(); }); function reqReadyStateChange() { if (request.readyState == 4) { if (request.status == 200) console.log(request.responseText); } }