← Назад к вопросам

Как браузер узнает, можно ли обратиться к серверу?

2.3 Middle🔥 111 комментариев
#Браузер и сетевые технологии

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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));

Что происходит:

  1. Браузер отправляет GET запрос с Header Origin: https://myapp.com
  2. Сервер отвечает с Access-Control-Allow-Origin: https://myapp.com
  3. Браузер разрешает доступ к ответу

Пример 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);
};

Заключение

Браузер проверяет доступ так:

  1. Сравнивает origins — одинаков ли схема, домен и порт?
  2. Отправляет preflight (для сложных запросов) — OPTIONS с info о запросе
  3. Проверяет CORS headers в ответе — разрешены ли this origin, method и headers?
  4. Выполняет основной запрос — если CORS headers верны, иначе блокирует

Ключевые headers:

  • Origin (запрос) — откуда идёт запрос
  • Access-Control-Allow-Origin (ответ) — кто может обращаться
  • Access-Control-Allow-Methods (ответ) — какие методы разрешены
  • Access-Control-Allow-Headers (ответ) — какие headers разрешены
  • Access-Control-Allow-Credentials (ответ) — можно ли отправлять cookies
Как браузер узнает, можно ли обратиться к серверу? | PrepBro