junior
Как работает CORS и как его настроить?
CORS (Cross-Origin Resource Sharing) — механизм, позволяющий браузеру делать запросы к серверу на другом домене (origin). По умолчанию браузер блокирует такие запросы из соображений безопасности (Same-Origin Policy).
Origin (источник)
Origin состоит из трёх частей: протокол + домен + порт.
Пример
https://app.mybank.com:443 — один origin
https://api.mybank.com:443 — другой origin (другой домен)
http://app.mybank.com:80 — другой origin (другой протокол и порт)
Простые и preflight-запросы
Простой запрос (не требует preflight):
- Метод: GET, HEAD, POST
- Заголовки: только стандартные (Accept, Content-Type с типом
text/plain,multipart/form-data,application/x-www-form-urlencoded)
Preflight-запрос (OPTIONS) выполняется перед основным запросом, если:
- Метод: PUT, DELETE, PATCH
- Нестандартные заголовки (например,
Authorization) Content-Type: application/json
Схема preflight-запроса
Браузер Сервер
│──── OPTIONS /api/transfer ──────────────────────>│
│ Origin: https://app.mybank.com │
│ Access-Control-Request-Method: POST │
│ Access-Control-Request-Headers: Authorization │
│<─── 200 OK ─────────────────────────────────────│
│ Access-Control-Allow-Origin: https://app.mybank.com
│ Access-Control-Allow-Methods: GET,POST,PUT │
│ Access-Control-Max-Age: 3600 │
│──── POST /api/transfer ─────────────────────────>│
│ Authorization: Bearer eyJhbG... │
│<─── 200 OK ─────────────────────────────────────│
Настройка CORS в Spring Boot
Пример конфигурации через WebMvcConfigurer
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins(
"https://app.mybank.com",
"https://admin.mybank.com"
)
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type", "X-Request-Id")
.exposedHeaders("X-Total-Count")
.allowCredentials(true)
.maxAge(3600);
}
}
Пример конфигурации через Spring Security
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(cors -> cors.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.mybank.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
return config;
}));
return http.build();
}
Настройка CORS в Nginx
Конфигурация Nginx
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' 'https://app.mybank.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type';
add_header 'Access-Control-Max-Age' 3600;
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://app.mybank.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
proxy_pass http://backend;
}
Важные правила безопасности
- Никогда не используйте
*вAccess-Control-Allow-Originдля критичных приложений — указывайте конкретные домены allowCredentials(true)несовместим сallowedOrigins("*")— это требование спецификации- Preflight-ответ кешируется на стороне браузера в течение
Access-Control-Max-Ageсекунд
На собеседовании: интервьюер проверяет понимание Same-Origin Policy, разницу между простым и preflight-запросом и умение настроить CORS в Spring. Частая ошибка — поставить
*в allowed origins «чтобы работало» и не понимать, почему preflight падает.