Что такое CORS и как этот механизм работает в браузере

Ответ

CORS (Cross-Origin Resource Sharing) — это механизм безопасности браузера, который позволяет веб-странице запрашивать ресурсы (например, API, шрифты, изображения) с домена, отличного от того, с которого была загружена сама страница.

Проблема, которую решает CORS

По умолчанию браузеры следуют политике одинакового источника (Same-Origin Policy, SOP), которая запрещает скриптам делать запросы к другому домену, протоколу или порту. Это защищает пользователей от вредоносных сайтов, которые могли бы читать данные с других открытых вкладок (например, из онлайн-банка).

CORS предоставляет серверам контролируемый способ ослабить эту политику и разрешить кросс-доменные запросы от доверенных источников.

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

  1. Простой запрос (например, GET или POST с простыми заголовками): браузер отправляет запрос, добавляя заголовок Origin с указанием домена-инициатора.
  2. Ответ сервера: Если сервер разрешает запрос с этого домена, он возвращает ответ с заголовком Access-Control-Allow-Origin: <домен> или Access-Control-Allow-Origin: * (разрешить всем).
  3. Предварительный запрос (Preflight Request): для "сложных" запросов (например, с методами PUT, DELETE или нестандартными заголовками) браузер сначала отправляет OPTIONS-запрос на тот же URL.
    • Сервер в ответ должен прислать заголовки, разрешающие метод, заголовки и источник (Access-Control-Allow-Methods, Access-Control-Allow-Headers).
    • Если preflight-запрос успешен, браузер отправляет основной запрос.

Пример настройки CORS в FastAPI (Python)

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Список доменов, которым разрешены запросы
origins = [
    "http://localhost:3000",
    "https://my-frontend-app.com",
]

# Добавление middleware для обработки CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins, # Разрешенные домены
    allow_credentials=True, # Разрешить cookies
    allow_methods=["GET", "POST"], # Разрешенные HTTP-методы
    allow_headers=["Authorization"], # Разрешенные HTTP-заголовки
)

@app.get("/api/data")
def get_data():
    return {"message": "This data is accessible via CORS"}

Если браузер не получит нужные заголовки от сервера, он заблокирует ответ, и в консоли разработчика появится ошибка CORS.

Ответ 18+ 🔞

О, слушай, вот тебе история про одну ебанутую защиту в браузерах, которая всех бесит, но на самом деле она нас спасает. Зовут её CORS, а расшифровывается как «Cross-Origin Resource Sharing», но всем похуй, главное — что она делает.

Представь себе, ты сидишь на сайте pornhub.com (ну, для примера). И твоя страничка, такая хитрая жопа, решает: «А не сходить ли мне на my-bank.com и не стырить ли данные пользователя?». Так вот, чтобы этого не случилось, браузеры придумали политику одинакового источника (Same-Origin Policy, SOP). Это такой злой батя, который говорит: «Нихуя, сынок, с этого домена — только на этот домен. Не лезь, куда не просят».

Но иногда нам, разработчикам, реально надо сделать запрос на другой домен — например, наш фронтенд на localhost:3000 хочет поговорить с бэкендом на localhost:8000. Вот тут-то и появляется наш «спаситель» — CORS. Это не отмена правил, а такой официальный пропуск, который сервер может выдать.

Как эта хуйня работает, по шагам:

  1. Твой скрипт пытается отправить запрос куда-то не туда. Браузер, такой бдительный страж, прикрепляет к запросу заголовок Origin: http://твой-сайт.com.
  2. Сервер получает запрос и решает: «А, этот чувак с того домена... Ну, окей, пусть». И в ответе он добавляет волшебный заголовок: Access-Control-Allow-Origin: http://твой-сайт.com (или звёздочку *, если он похуист и пускает всех).
  3. Браузер видит этот заголовок и говорит: «Ну ладно, раз сервер разрешил — проходи». И отдаёт ответ твоему скрипту.

Но это ещё цветочки! Бывают сложные запросы — не GET или POST, а всякие PUT, DELETE, или с какими-то нестандартными заголовками вроде Authorization. Тут браузер вообще параноиком становится и делает предварительный запрос (Preflight).

Он сначала шлёт запрос методом OPTIONS (типа «эй, сервак, а можно так?»), и ждёт, чтобы сервер ответил: «Да, можно метод PUT, да, можно заголовок X-My-Header, и вообще заходи». И только после этого, ебать, отправляет настоящий запрос. Если сервер на OPTIONS не ответил как надо — всё, пиздец, основной запрос даже не уйдёт.

А вот как это настроить, например, на FastAPI, чтобы не орать потом «почему не работает, блядь?»:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Список доменов, которым разрешены запросы
origins = [
    "http://localhost:3000",
    "https://my-frontend-app.com",
]

# Добавление middleware для обработки CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=origins, # Разрешенные домены
    allow_credentials=True, # Разрешить cookies
    allow_methods=["GET", "POST"], # Разрешенные HTTP-методы
    allow_headers=["Authorization"], # Разрешенные HTTP-заголовки
)

@app.get("/api/data")
def get_data():
    return {"message": "This data is accessible via CORS"}

Если ты всё настроил, а в консоли браузера всё равно красуется ошибка про CORS — значит, сервер нихуя не разрешил. Либо домен не тот, либо метод, либо заголовки. Волнение ебать, но надо просто проверить, что сервер шлёт в ответах на твои запросы. Вот и вся магия, ёпта.