Как браузер узнает, можно ли обратиться к серверу?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
CORS: Как браузер проверяет доступ к серверу
Вопрос про CORS (Cross-Origin Resource Sharing) — механизм безопасности браузера, который контролирует доступ к ресурсам с других доменов.
Проблема: Same-Origin Policy
Same-Origin Policy — это базовое правило безопасности браузера:
- Скрипты с сайта A могут обращаться только к ресурсам на сайте A
- Обращение к сайту B блокируется по умолчанию
// На сайте https://myapp.com
fetch('https://api.example.com/data'); // Блокируется CORS!
Это защита от:
- Кража cookie и токенов
- Несанкционированные запросы от имени пользователя
- Шпионаж за данными пользователя
Как браузер проверяет доступ
Шаг 1: Определение Origin
Браузер сравнивает источники:
// Origin = Scheme + Domain + Port
// Мой сайт
https://myapp.com:443
^
Scheme (https)
https://myapp.com:443
^
Domain
https://myapp.com:443
^
Port
// Запрос к другому серверу
https://api.example.com:443
// Origin НЕ совпадает -> нужна проверка CORS
Шаг 2: Preflight запрос (для "сложных" запросов)
Для запросов с особыми условиями браузер автоматически отправляет preflight (запрос-разведка):
Первичный запрос (в приложении):
POST /api/data
Content-Type: application/json
Authorization: Bearer token123
Браузер перехватывает и отправляет preflight:
OPTIONS /api/data
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,authorization
Когда браузер отправляет preflight:
- HTTP методы: POST, PUT, DELETE, PATCH
- Custom headers: Authorization, X-Custom-Header
- Content-Type: application/json, application/xml
Когда НЕ отправляет preflight ("простые" запросы):
- Методы: GET, HEAD, POST (с form data)
- Headers: Content-Type (только form-related), Accept, Accept-Language
Шаг 3: Сервер отправляет CORS headers
Сервер отвечает на preflight:
OPTIONS /api/data
Ответ сервера:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 3600
Что означают headers:
Access-Control-Allow-Origin— какие origins разрешеныAccess-Control-Allow-Methods— какие HTTP методы разрешеныAccess-Control-Allow-Headers— какие headers можно отправлятьAccess-Control-Max-Age— как долго кэшировать эту информацию (3600 сек)Access-Control-Allow-Credentials— разрешить ли отправлять cookies
Шаг 4: Браузер проверяет и выполняет основной запрос
Если ответ успешный — браузер отправляет основной запрос:
POST /api/data
Origin: https://myapp.com
Content-Type: application/json
Authorization: Bearer token123
Ответ с основным сервером:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Content-Type: application/json
{ "data": "..." }
Если ответ на preflight не содержит нужные CORS headers — браузер блокирует основной запрос и выбрасывает ошибку в консоль.
Примеры в коде
Пример 1: Простой GET запрос (без preflight)
// На https://myapp.com
fetch('https://api.example.com/users')
.then(res => res.json())
.then(data => console.log(data));
Что происходит:
- Браузер отправляет GET запрос с Header
Origin: https://myapp.com - Сервер отвечает с
Access-Control-Allow-Origin: https://myapp.com - Браузер разрешает доступ к ответу
Пример 2: POST с JSON (с preflight)
fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
},
body: JSON.stringify({ name: 'John' })
});
Что происходит:
1. Браузер отправляет preflight (OPTIONS):
OPTIONS /users
Origin: https://myapp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,authorization
2. Сервер отвечает:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
3. Браузер отправляет основной запрос (POST)
4. Браузер проверяет заголовки ответа и разрешает доступ
Пример 3: С отправкой cookies
fetch('https://api.example.com/profile', {
method: 'GET',
credentials: 'include' // Отправляем cookies
});
На сервере нужно:
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
ВАЖНО: Если используется credentials: 'include':
Access-Control-Allow-OriginНЕ может быть*(только конкретный origin)Access-Control-Allow-Credentialsдолжен бытьtrue
Конфигурация сервера
Express (Node.js)
const cors = require('cors');
const app = require('express')();
// Разрешить конкретный origin
app.use(cors({
origin: 'https://myapp.com',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}));
// Вручную
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', 'https://myapp.com');
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.header('Access-Control-Allow-Credentials', 'true');
if (req.method === 'OPTIONS') {
res.sendStatus(200);
} else {
next();
}
});
FastAPI (Python)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://myapp.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
)
Nginx
server {
location /api {
add_header Access-Control-Allow-Origin "https://myapp.com";
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, Authorization";
add_header Access-Control-Allow-Credentials "true";
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend:3000;
}
}
Ошибки CORS
Ошибка 1: CORS error в консоли
Access to XMLHttpRequest at 'https://api.example.com/data' from origin
'https://myapp.com' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Решение: Сервер должен отправить Access-Control-Allow-Origin header
Ошибка 2: Credentials с wildcard
Access to fetch at 'https://api.example.com/data' from origin
'https://myapp.com' has been blocked by CORS policy:
The value of the 'Access-Control-Allow-Credentials' header in the response
is '' which must be 'true' when the request's credentials mode is 'include'.
Решение: Нельзя использовать * с credentials:
// Неправильно
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
// Правильно
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Credentials: true
Обход CORS (антипаттерны)
Антипаттерн 1: CORS proxy
// НЕ ДЕЛАТЬ!
fetch('https://cors-anywhere.herokuapp.com/https://api.example.com/data')
Проксирует запрос через третий сервер — небезопасно и медленно.
Антипаттерн 2: Same-origin backend
// Правильно!
// На https://myapp.com, запрос к https://myapp.com/api/...
// Backend перенаправляет на внешний API
fetch('/api/external-data');
Все запросы идут на свой сервер, тот уже обращается к внешним API.
Современные альтернативы
Server-Sent Events (SSE)
Одностороннее соединение с сервером (без CORS проблем для запросов):
const eventSource = new EventSource('https://api.example.com/events');
eventSource.onmessage = (event) => {
console.log('Message:', event.data);
};
WebSocket
Двусторонний канал связи:
const ws = new WebSocket('wss://api.example.com/ws');
ws.onmessage = (event) => {
console.log('Data:', event.data);
};
Заключение
Браузер проверяет доступ так:
- Сравнивает origins — одинаков ли схема, домен и порт?
- Отправляет preflight (для сложных запросов) — OPTIONS с info о запросе
- Проверяет CORS headers в ответе — разрешены ли this origin, method и headers?
- Выполняет основной запрос — если CORS headers верны, иначе блокирует
Ключевые headers:
Origin(запрос) — откуда идёт запросAccess-Control-Allow-Origin(ответ) — кто может обращатьсяAccess-Control-Allow-Methods(ответ) — какие методы разрешеныAccess-Control-Allow-Headers(ответ) — какие headers разрешеныAccess-Control-Allow-Credentials(ответ) — можно ли отправлять cookies