Gymterview
middle

Как настроить безопасность REST API с помощью Spring Security?

Spring Security 6 полностью перешёл на компонентную модель с SecurityFilterChain, заменив устаревший WebSecurityConfigurerAdapter.

Конфигурация SecurityFilterChain
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable()) // Для stateless API
            .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/v1/public/**").permitAll()
                .requestMatchers("/actuator/health", "/actuator/info").permitAll()
                .requestMatchers(HttpMethod.POST, "/api/v1/orders").hasRole("USER")
                .requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter()))
            )
            .build();
    }

    @Bean
    public JwtAuthenticationConverter jwtAuthConverter() {
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter =
            new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");

        JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
        converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return converter;
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowedOrigins(List.of("https://app.example.com"));
        config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
        config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
        config.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/api/**", config);
        return source;
    }
}

Rate Limiting (Bucket4j + Redis)

RateLimitInterceptor
@Component
public class RateLimitInterceptor implements HandlerInterceptor {

    private final ProxyManager<String> proxyManager;

    @Override
    public boolean preHandle(HttpServletRequest request,
            HttpServletResponse response, Object handler) {
        String clientId = extractClientId(request);

        Bucket bucket = proxyManager.builder()
            .build(clientId, () -> BucketConfiguration.builder()
                .addLimit(Bandwidth.builder()
                    .capacity(100)
                    .refillGreedy(100, Duration.ofMinutes(1))
                    .build())
                .build());

        if (bucket.tryConsume(1)) {
            return true;
        }
        response.setStatus(429);
        return false;
    }
}

Ключевые принципы

  • Stateless API: SessionCreationPolicy.STATELESS + JWT. Никаких HTTP-сессий
  • CSRF отключается для stateless REST API. Для Thymeleaf CSRF обязателен
  • Method Security (@PreAuthorize, @Secured) — для fine-grained авторизации
  • Secrets никогда не хранятся в коде — переменные окружения, Vault, Kubernetes Secrets

Частые ошибки

  • Хранение JWT в localStorage — уязвимость к XSS. Для браузеров: HttpOnly cookie с SameSite=Strict
  • Отсутствие rate limiting — уязвимость к DDoS и brute-force
  • Логирование токенов и паролей — критическая уязвимость
  • Использование @Secured(“ROLE_ADMIN”) без юнит-тестов на авторизацию

На собеседовании: покажите, что знаете новый API (SecurityFilterChain вместо WebSecurityConfigurerAdapter). Обязательные пункты: STATELESS для API, CSRF отключён для REST но включён для HTML, Method Security для бизнес-правил. Вопрос про хранение JWT в браузере — ответ: HttpOnly cookie, никогда localStorage.