Урок по треку Spring и Spring Boot 8 мин

Spring Security

Цепочка фильтров, аутентификация и авторизация, JWT и OAuth2, CSRF и CORS.

Spring Security кажется магией, пока не увидишь, что это просто цепочка фильтров. На собесе спрашивают, как проходит запрос через Security, чем аутентификация отличается от авторизации, как сделать JWT и OAuth2, что такое CSRF и CORS.

Аутентификация и авторизация

  • Аутентификация: кто ты (логин и пароль, токен, сертификат). Ошибка даёт 401 Unauthorized.
  • Авторизация: что тебе можно (роли, права). Ошибка даёт 403 Forbidden.

Цепочка фильтров

Spring Security встраивается в сервлет-контейнер одним фильтром DelegatingFilterProxy, который передаёт запрос в FilterChainProxy, а тот в подходящую SecurityFilterChain. Внутри цепочки фильтры по порядку: CORS, CSRF, аутентификация (форма, Basic, Bearer-токен), обработка исключений (ExceptionTranslationFilter), авторизация (AuthorizationFilter).

Результат аутентификации кладётся в SecurityContextHolder (по умолчанию ThreadLocal), откуда его берут контроллеры, @PreAuthorize и аудит.

Конфигурация в Security 6

WebSecurityConfigurerAdapter удалён, конфигурация это бин SecurityFilterChain и лямбда-DSL:

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    SecurityFilterChain api(HttpSecurity http) throws Exception {
        return http
            .csrf(csrf -> csrf.disable())                 // stateless API с токенами
            .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/public/**", "/actuator/health").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
            .build();
    }
}

Правила проверяются сверху вниз до первого совпадения: широкое правило выше узкого перекроет его. Заканчивать стоит anyRequest().authenticated() или denyAll().

hasRole("ADMIN") проверяет authority ROLE_ADMIN, а hasAuthority("ADMIN") ровно ADMIN. Путаница префикса частая причина 403.

Пароли

Пароли хранят только хешем с солью медленным алгоритмом: BCryptPasswordEncoder, Argon2, PBKDF2. PasswordEncoderFactories.createDelegatingPasswordEncoder() пишет префикс {bcrypt} и позволяет мигрировать алгоритмы. MD5, SHA-256 без соли и обратимое шифрование не годятся.

JWT и OAuth2

Для API без сессий клиент присылает Authorization: Bearer <jwt>. Resource Server (spring-boot-starter-oauth2-resource-server) проверяет подпись по ключам издателя (issuer-uri или jwk-set-uri), срок, издателя и превращает claims в authorities.

  • Access-токен короткий (минуты), refresh-токен длинный, хранится безопасно и ротируется.
  • JWT нельзя отозвать до истечения без чёрного списка: поэтому короткий срок.
  • В JWT не кладут секреты: payload только закодирован base64url, не зашифрован.
  • Алгоритм проверки задаётся сервером, а не берётся из заголовка токена (атака alg: none).

Логин через внешнего провайдера (Keycloak, Google) это oauth2Login и Authorization Code с PKCE.

Методная безопасность

@EnableMethodSecurity включает @PreAuthorize("hasRole('ADMIN')"), @PreAuthorize("#userId == authentication.name"). Работает через прокси: те же ограничения, что у @Transactional (this-вызовы, private).

CSRF и CORS

  • CSRF: чужой сайт отправляет запрос от имени пользователя, браузер сам прикладывает cookie сессии. Защита включена по умолчанию (токен в форме или заголовке). Для stateless API с токеном в заголовке Authorization её обычно выключают: браузер этот заголовок сам не подставит. Если аутентификация через cookie, CSRF выключать нельзя.
  • CORS: браузер разрешает фронтенду с другого origin читать ответы, только если сервер вернул нужные заголовки. Настраивается http.cors(...) и CorsConfigurationSource. allowedOrigins("*") вместе с allowCredentials(true) запрещено.

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

  • permitAll выше правил с ограничениями или забытый anyRequest().
  • hasRole против hasAuthority и префикс ROLE_.
  • Выключенный CSRF при cookie-аутентификации.
  • Длинноживущие JWT без возможности отзыва, секреты в payload.
  • Проверка прав только на фронтенде.

Что спросят на собесе

  • Как запрос проходит через Spring Security? Что такое SecurityFilterChain?
  • Аутентификация против авторизации, 401 против 403.
  • Как хранить пароли?
  • Как устроена проверка JWT в Resource Server, как отзывать токены?
  • Что такое CSRF и когда его можно выключить? Чем CSRF отличается от CORS?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.