Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое CORS?
CORS (Cross-Origin Resource Sharing) — это механизм, который позволяет веб-приложениям, работающим в браузере, получать доступ к ресурсам с других доменов. Без CORS браузер блокирует такие запросы по соображениям безопасности.
Проблема: Same-Origin Policy
Браузер имеет встроенную политику безопасности:
Оригин = Схема (HTTP/HTTPS) + Домен + Порт
Примеры разных оригинов:
https://example.com:443
https://example.com:8443 ← Разный порт!
https://api.example.com ← Разный поддомен!
http://example.com ← Разная схема!
Приложение на https://example.com не может сделать AJAX запрос к https://api.example.com без специальных разрешений.
Как это работает
Простые запросы (GET, POST с стандартными заголовками):
1. Браузер отправляет запрос с заголовком Origin:
POST /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
2. Сервер проверяет Origin и отправляет ответ с разрешением:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
3. Браузер видит заголовок и разрешает приложению использовать ответ
Сложные запросы (PUT, DELETE, кастомные заголовки):
1. Браузер отправляет preflight запрос (OPTIONS):
OPTIONS /api/users HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
2. Сервер разрешает или запрещает:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 3600
3. Только если preflight успешен, браузер отправляет основной запрос
Реализация в Java Spring
Вариант 1: На уровне контроллера
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(origins = "https://app.example.com")
@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
@CrossOrigin(origins = {"https://app.example.com", "https://admin.example.com"})
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}
Вариант 2: Глобальная конфигурация (рекомендуется)
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://app.example.com", "https://admin.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*") // Разрешить все заголовки
.allowCredentials(true) // Разрешить cookies
.maxAge(3600); // Кешировать preflight на 1 час
}
}
Вариант 3: Через фильтр
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
@Component
public class CorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
String origin = request.getHeader("Origin");
if (isAllowedOrigin(origin)) {
response.setHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Max-Age", "3600");
}
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
try {
filterChain.doFilter(request, response);
} catch (Exception e) {
e.printStackTrace();
}
}
private boolean isAllowedOrigin(String origin) {
return origin != null &&
(origin.equals("https://app.example.com") ||
origin.equals("https://admin.example.com"));
}
}
CORS заголовки
| Заголовок | Значение | Значение |
|---|---|---|
Access-Control-Allow-Origin | * или конкретный origin | Какие origins могут получать ответы |
Access-Control-Allow-Methods | GET, POST, PUT, DELETE | Какие HTTP методы разрешены |
Access-Control-Allow-Headers | Content-Type, Authorization | Какие заголовки разрешены |
Access-Control-Allow-Credentials | true | Разрешить ли cookies и auth |
Access-Control-Max-Age | 3600 | Кешировать preflight на N секунд |
Access-Control-Expose-Headers | X-Custom-Header | Какие заголовки видны клиенту |
Безопасность
Опасно:
@CrossOrigin(origins = "*") // Разрешить ВСЕМ!
Это позволяет ЛЮБОМУ веб-сайту делать запросы к вашему API.
Лучше:
@CrossOrigin(origins = {"https://app.example.com"})
allowCredentials = false // Если используется *
И никогда не используйте CORS для защиты от security-атак — всегда проверяйте на сервере!
Резюме
- CORS необходим для cross-domain запросов из браузера
- Браузер автоматически отправляет
Originзаголовок - Сервер должен ответить с
Access-Control-Allow-Origin - Для сложных запросов используется preflight (OPTIONS)
- Всегда явно указывайте allowed origins, не используйте
*для credentials