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

Какие знаешь способы фильтрации запросов по заголовку?

1.7 Middle🔥 191 комментариев
#Основы Java

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Способы фильтрации запросов по заголовку

Фильтрация 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 — authentication
  • X-Request-ID — request tracing
  • X-Idempotency-Key — deduplication
  • X-Version — API версия
  • X-Client-ID — identification
  • X-Correlation-ID — распределённый tracing
  • Accept-Language — локализация
  • Authorization — OAuth2/JWT token

В целом: правильная фильтрация заголовков — это критичная часть security и stability приложения. Выбирай подход в зависимости от требований: простые фильтры для базовой валидации, AOP для более сложной логики, Gateway для микросервисов.

Какие знаешь способы фильтрации запросов по заголовку? | PrepBro