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.