SecurityFilterChain
Spring Security — это цепочка сервлет-фильтров ПЕРЕД DispatcherServlet (а не MVC-interceptor!). Запрос проходит через фильтры, каждый из которых отвечает за свой аспект: извлечь credentials, построить Authentication, проверить авторизацию, обработать отказ. Поэтому security срабатывает раньше контроллеров.
Современная конфигурация — это бин SecurityFilterChain (через HttpSecurity DSL). Старый WebSecurityConfigurerAdapter удалён в Spring Security 6, поэтому актуальные примеры — на бинах.
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.csrf(c -> c.disable()) // stateless API
.sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
.authorizeHttpRequests(a -> a
.requestMatchers("/actuator/health").permitAll()
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.build();
}

Аутентификация
За проверку «кто ты» отвечает AuthenticationManager, делегирующий AuthenticationProvider'ам. Для логина по логину/паролю UserDetailsService загружает пользователя (из БД), а PasswordEncoder сверяет пароль.
Пароли ВСЕГДА хранят хешированными медленным алгоритмом с солью — BCrypt (или Argon2/scrypt). Никогда не хранить в открытом виде и не использовать быстрые хеши (MD5/SHA) — их перебирают мгновенно.
SecurityContext
После успешной аутентификации результат — Authentication (с principal и authorities) — кладётся в SecurityContextHolder, который по умолчанию держит его в ThreadLocal текущего запроса. Отсюда его берут и фильтры авторизации, и прикладной код (@AuthenticationPrincipal).
В stateless API контекст не хранится между запросами в сессии — он восстанавливается на КАЖДЫЙ запрос из bearer-токена соответствующим фильтром. Из-за ThreadLocal контекст не «переезжает» в другой поток (@Async) без специальной настройки.
Авторизация запросов
authorizeHttpRequests задаёт правила доступа по матчерам: requestMatchers("/admin/**").hasRole("ADMIN"), .authenticated(), .permitAll(). Правила проверяются по порядку — от частных к общему, а anyRequest() идёт последним.
Тонкость roles vs authorities: hasRole("ADMIN") внутри проверяет authority "ROLE_ADMIN" (префикс ROLE_ добавляется автоматически), а hasAuthority("...") сверяет строку как есть. Путаница с префиксом ROLE_ — частая причина «почему доступ не работает».
CSRF, CORS, сессии
CSRF-защита включена по умолчанию и нужна для stateful-сценариев (cookie/сессия, формы), где браузер сам шлёт credentials. Для stateless bearer-API (токен в заголовке Authorization, браузер не добавляет его автоматически) CSRF обычно отключают — и одновременно ставят session policy STATELESS.
CORS — это браузерная политика кросс-доменных запросов, а не авторизация; её настраивают в Security/MVC. XSS предотвращают на уровне вывода (экранирование, CSP) и тем, что чувствительные токены не кладут в доступное JS место.
Как отвечать на собеседовании
Объясните, что Security — цепочка фильтров до DispatcherServlet (не interceptor), и конфигурируется бином SecurityFilterChain (адаптер удалён в SS6). Разберите аутентификацию (UserDetailsService + PasswordEncoder=BCrypt, пароли только хешированные), SecurityContext в ThreadLocal, авторизацию запросов с ловушкой ROLE_-префикса, и когда отключать CSRF (stateless bearer).