Какие знаешь способы фильтрации запросов по заголовку?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Способы фильтрации запросов по заголовку
Фильтрация HTTP запросов по заголовкам (headers) — это критичная часть security и логики приложения. Расскажу о подходах, которые использую в production.
1. Spring Security HeadersAuthenticationFilter
Создай custom фильтр для проверки заголовков:
@Component
public class CustomHeaderFilter extends OncePerRequestFilter {
private final Logger logger = LoggerFactory.getLogger(CustomHeaderFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
// Проверяем обязательные заголовки
String apiKey = request.getHeader("X-API-Key");
String clientId = request.getHeader("X-Client-ID");
String requestId = request.getHeader("X-Request-ID");
// Если заголовок отсутствует
if (apiKey == null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Missing X-API-Key header");
return;
}
// Валидируем API ключ
if (!isValidApiKey(apiKey)) {
logger.warn("Invalid API key: {}", apiKey);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("Invalid API key");
return;
}
// Добавляем в контекст для использования в контроллере
request.setAttribute("clientId", clientId);
request.setAttribute("requestId", requestId);
// Продолжаем цепь фильтров
filterChain.doFilter(request, response);
}
private boolean isValidApiKey(String apiKey) {
// Проверяем API ключ (из БД, кэша или конфига)
return apiKeyRepository.existsByKey(apiKey);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http,
CustomHeaderFilter headerFilter)
throws Exception {
http.addFilterBefore(headerFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
2. Фильтрация с помощью аннотаций (@RequestHeader)
Spring позволяет фильтровать через параметры метода:
@RestController
@RequestMapping("/api/v1")
public class DataController {
@GetMapping("/data")
public ResponseEntity<DataDTO> getData(
@RequestHeader(value = "X-API-Key", required = true) String apiKey,
@RequestHeader(value = "X-Client-ID", required = false) String clientId,
@RequestHeader(value = "X-Request-ID", defaultValue = "unknown") String requestId
) {
logger.info("Request from client: {} with requestId: {}", clientId, requestId);
// Валидируем API ключ
if (!validateApiKey(apiKey)) {
throw new UnauthorizedException("Invalid API key");
}
return ResponseEntity.ok(dataService.getData());
}
@PostMapping("/data")
public ResponseEntity<DataDTO> postData(
@RequestHeader("X-API-Key") String apiKey,
@RequestHeader("X-Idempotency-Key", required = false) String idempotencyKey,
@RequestBody DataRequest request
) {
// Обработка idempotency key
if (idempotencyKey != null) {
DataDTO cachedResult = cache.getIfPresent(idempotencyKey);
if (cachedResult != null) {
return ResponseEntity.ok(cachedResult);
}
}
DataDTO result = dataService.create(request);
if (idempotencyKey != null) {
cache.put(idempotencyKey, result);
}
return ResponseEntity.status(HttpStatus.CREATED).body(result);
}
}
3. Interceptor для фильтрации заголовков
Spring MVC Interceptor для обработки headers:
@Component
public class HeaderValidationInterceptor implements HandlerInterceptor {
private final Logger logger = LoggerFactory.getLogger(HeaderValidationInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler)
throws Exception {
// Проверяем Content-Type для POST/PUT запросов
if ("POST".equals(request.getMethod()) || "PUT".equals(request.getMethod())) {
String contentType = request.getHeader("Content-Type");
if (contentType == null || !contentType.contains("application/json")) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getWriter().write("Content-Type must be application/json");
return false;
}
}
// Проверяем User-Agent
String userAgent = request.getHeader("User-Agent");
if (userAgent == null || userAgent.isEmpty()) {
logger.warn("Request without User-Agent header");
}
// Проверяем CORS headers
String origin = request.getHeader("Origin");
if (origin != null && !isAllowedOrigin(origin)) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return false;
}
return true;
}
private boolean isAllowedOrigin(String origin) {
List<String> allowedOrigins = List.of(
"https://example.com",
"https://app.example.com"
);
return allowedOrigins.contains(origin);
}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HeaderValidationInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/health");
}
}
4. HttpComponentsClientHttpRequestFactory для клиента
Фильтрация заголовков при отправке запросов:
@Configuration
public class HttpClientConfig {
@Bean
public RestTemplate restTemplate() {
HttpComponentsClientHttpRequestFactory factory =
new HttpComponentsClientHttpRequestFactory();
return new RestTemplate(factory);
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
HttpClientBuilder builder = HttpClientBuilder.create()
.addInterceptorFirst(new HttpRequestInterceptor() {
@Override
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException {
// Добавляем или фильтруем заголовки
request.addHeader("X-API-Key", apiKeyProvider.getKey());
request.addHeader("User-Agent", "MyApp/1.0");
request.addHeader("X-Request-ID", UUID.randomUUID().toString());
}
});
return new HttpComponentsClientHttpRequestFactory(
builder.build()
);
}
}
5. Использование AOP для фильтрации заголовков
Aspect для проверки headers на методе:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireHeader {
String value(); // Имя заголовка
boolean required() default true;
String[] allowedValues() default {};
}
@Aspect
@Component
public class HeaderValidationAspect {
private final Logger logger = LoggerFactory.getLogger(HeaderValidationAspect.class);
@Before("@annotation(requireHeader)")
public void validateHeader(JoinPoint joinPoint, RequireHeader requireHeader) {
ServletRequestAttributes attributes =
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
String headerValue = request.getHeader(requireHeader.value());
// Проверяем наличие
if (requireHeader.required() && headerValue == null) {
throw new MissingHeaderException(
"Required header '" + requireHeader.value() + "' is missing"
);
}
// Проверяем значение
if (headerValue != null && requireHeader.allowedValues().length > 0) {
boolean isAllowed = Arrays.asList(requireHeader.allowedValues())
.contains(headerValue);
if (!isAllowed) {
throw new InvalidHeaderValueException(
"Header '" + requireHeader.value() + "' has invalid value"
);
}
}
}
}
// Использование
@RestController
@RequestMapping("/api/v1")
public class AdminController {
@GetMapping("/admin/users")
@RequireHeader(value = "X-Admin-Token", required = true)
public ResponseEntity<List<UserDTO>> getAdminUsers() {
return ResponseEntity.ok(userService.getAllUsers());
}
@PostMapping("/data")
@RequireHeader(value = "X-Version", allowedValues = {"v1", "v2"})
public ResponseEntity<Void> postData(@RequestBody DataRequest request) {
// ...
return ResponseEntity.ok().build();
}
}
6. Фильтрация с помощью Spring Cloud Gateway
Gateway filter для фильтрации заголовков:
@Component
public class AuthorizationHeaderFilter extends AbstractGatewayFilterFactory<AuthorizationHeaderFilter.Config> {
public static class Config {
private String requiredHeader;
private boolean validateApiKey;
}
public AuthorizationHeaderFilter() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// Проверяем заголовок
String headerValue = request.getHeaders().getFirst(config.requiredHeader);
if (headerValue == null && config.validateApiKey) {
return onError(exchange, "Missing required header: " + config.requiredHeader);
}
// Валидируем API ключ
if (config.validateApiKey && !isValidApiKey(headerValue)) {
return onError(exchange, "Invalid API key");
}
return chain.filter(exchange);
};
}
private Mono<Void> onError(ServerWebExchange exchange, String message) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
return response.writeWith(Mono.just(dataBuffer));
}
}
// application.yml
spring:
cloud:
gateway:
routes:
- id: user-service
uri: lb://user-service
predicates:
- Path=/api/users/**
filters:
- AuthorizationHeaderFilter=X-API-Key,true
7. Условная фильтрация с помощью @ConditionalOnHeader
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ConditionalOnHeader {
String name();
String value() default "";
boolean required() default false;
}
@Component
public class ConditionalHeaderFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
// Пример: включить debug логирование если X-Debug header присутствует
String debugHeader = httpRequest.getHeader("X-Debug");
if ("true".equals(debugHeader)) {
MDC.put("debug", "true");
}
try {
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
8. Best Practices для фильтрации headers
Безопасность:
- Белый список заголовков — только разрешённые headers пропускай
- Валидируй формат — проверяй длину, символы
- Защищайся от injection — экранируй специальные символы
- Логируй подозрительные запросы — но не чувствительные данные
Производительность:
- Кэшируй результаты валидации — например, API ключи
- Используй быстрые проверки — regex может быть медленным
- Избегай N+1 проблем — валидация не должна запускать БД запросы на каждый header
Удобство:
- Документируй обязательные headers в API документации
- Возвращай понятные ошибки — что именно не хватает
- Поддерживай несколько версий API с разными headers
Примеры полезных headers:
X-API-Key— authenticationX-Request-ID— request tracingX-Idempotency-Key— deduplicationX-Version— API версияX-Client-ID— identificationX-Correlation-ID— распределённый tracingAccept-Language— локализацияAuthorization— OAuth2/JWT token
В целом: правильная фильтрация заголовков — это критичная часть security и stability приложения. Выбирай подход в зависимости от требований: простые фильтры для базовой валидации, AOP для более сложной логики, Gateway для микросервисов.