Spring Security¶
Аутентификация¶
Аутентификация почти всегда проходит по одной и той же схеме:
1. Filter извлекает credentials из запроса
2. Создает Authentication object (еще не authenticated)
3. Передает его в AuthenticationManager
4. AuthenticationManager -> AuthenticationProvider
5. Provider проверяет пользователя и credentials
6. Возвращается новый authenticated Authentication
7. Он кладется в SecurityContext
8. Запрос идет дальше как от уже аутентифицированного пользователя
POST /login
username=alice
password=secret
UsernamePasswordAuthenticationFilter
-> создает UsernamePasswordAuthenticationToken(unauthenticated)
-> AuthenticationManager.authenticate(...)
-> ProviderManager
-> DaoAuthenticationProvider
-> UserDetailsService.loadUserByUsername("alice")
-> PasswordEncoder.matches(raw, encoded)
-> вернуть authenticated token
-> SecurityContextHolder.getContext().setAuthentication(authenticatedToken)
Basic Auth¶
Authorization: Basic base64(username:password)
BasicAuthenticationFilter
-> извлекает header
-> декодирует username/password
-> создает Authentication
-> AuthenticationManager
-> ProviderManager / DaoAuthenticationProvider
-> SecurityContext
JWT Bearer Token¶
Authorization: Bearer <jwt>
BearerTokenAuthenticationFilter
-> достает token из header
-> AuthenticationManager / AuthenticationProvider
-> проверка подписи, exp, aud, iss, claims
-> создается JwtAuthenticationToken
-> SecurityContext
При JWT обычно нет server-side session, и аутентификация выполняется на каждом запросе заново по токену.
Авторизация¶
После аутентификации Spring Security проверяет: имеет ли текущий пользователь право на этот URL / HTTP method / method call
В современном servlet stack это обычно делает AuthorizationFilter.
AuthorizationFilter
-> берет текущий Authentication из SecurityContextHolder
-> передает его в AuthorizationManager
-> AuthorizationManager решает grant / deny
-> если grant -> запрос идет дальше
-> если deny -> AccessDeniedException
Архитектура Spring Security¶
HTTP request
> Servlet Container
-------------------------------
> DelegatingFilterProxy
> FilterChainProxy
> SecurityFilterChain
> Security Filters
> SecurityContext (load)
> HeaderWriterFilter
> CorsFilter / CsrfFilter
> LogoutFilter
> Authentication filter
> AuthenticationManager
> AuthenticationProvider
> UserDetailsService / external auth source
> AnonymousAuthenticationFilter
> ExceptionTranslationFilter
> AuthorizationFilter
> SecurityContext (save/clear)
> DispatcherServlet
> Controller
> Method Security
> Service
> HTTP response
Servlet container - не знает про Spring Security напрямую. DelegatingFilterProxy - мост между
servlet и Spring context Spring подставляет туда DelegatingFilterProxy,
а он передает работу Spring-beanу. Этим bean'ом обычно является FilterChainProxy.
FilterChainProxy - главный координатор security-фильтров. Он выбирает подходящую
SecurityFilterChain. Уже внутри нее выполняются security filters.
DelegatingFilterProxy - Это bridge между servlet container и Spring ApplicationContext.
Это мост между Servlet Container и Spring Context.
Servlet container знает только обычный servlet filter, а DelegatingFilterProxy уже делегирует
выполнение Spring-beanу, обычно springSecurityFilterChain.
Servlet container умеет регистрировать только Filter, но не знает, как искать Spring beans.
DelegatingFilterProxy решает эту проблему: он сам зарегистрирован как servlet filter, но делегирует
работу Spring-beanу.
FilterChainProxy - Центральный filter Spring Security. Это главный координационный объект всей web security.
- принять запрос,
- найти подходящую SecurityFilterChain,
- выполнить filters в правильном порядке,
- очистить контекст после завершения запроса,
- применить HttpFirewall.
Это центральный security-filter Spring Security. Он управляет всеми security filters и является стартовой точкой servlet security architecture. Spring docs прямо говорят, что security filters обычно регистрируются именно через FilterChainProxy.
SecurityFilterChain - правило вида: если request подходит под matcher -> применить нужный набор security filters
@Bean
SecurityFilterChain apiChain(HttpSecurity http) { ... } // /api/**
@Bean
SecurityFilterChain webChain(HttpSecurity http) { ... } // все остальное
FilterChainProxy выбирает первую подходящую цепочку,
- цепочки могут быть разными для API, admin, actuator, статических ресурсов,
- у каждой цепочки свой matcher и свой набор правил.
FilterChainProxy выбирает подходящую SecurityFilterChain для конкретного запроса по matcher’ам. То есть у тебя может быть одна цепочка для /api/, другая для /admin/, третья для всего остального. Это и есть архитектурная модель Spring Security web layer.
HttpSecurity — DSL-билдер, через который настраивается SecurityFilterChain. Через него добавляется:
- правила авторизации,
- form login,
- basic auth,
- JWT resource server,
- logout,
- session management,
- CSRF,
- CORS,
- custom filters.
HttpSecurity -> строит SecurityFilterChain
SecurityFilterChain -> используется FilterChainProxy
FilterChainProxy -> реально исполняет защиту на запросе
¶
HttpSecurity -> строит SecurityFilterChain
SecurityFilterChain -> используется FilterChainProxy
FilterChainProxy -> реально исполняет защиту на запросе
После выбора SecurityFilterChain начинают работать Security filters.
- Загрузка
SecurityContext
Spring Security сначала работает с SecurityContext — контекстом безопасности текущего запроса.
По умолчанию SecurityContextHolder хранит его в ThreadLocal, а FilterChainProxy гарантирует
корректную очистку после обработки запроса. 
Authentication filter
Один из authentication filters извлекает учетные данные из запроса. Это может быть: - username/password, - bearer token, - basic auth header, - session/cookie, - OAuth2 login response.
Конкретный фильтр зависит от configured authentication mechanism.
AuthenticationManager
Фильтр передает запрос на аутентификацию в AuthenticationManager.
Это главный orchestrator authentication flow. 
AuthenticationProvider
Обычно AuthenticationManager реализован как ProviderManager, который делегирует проверку одному
или нескольким AuthenticationProvider. Spring docs прямо говорят, что default
implementation — ProviderManager, который опрашивает список AuthenticationProvider. 
- Проверка пользователя
AuthenticationProvider уже делает реальную проверку: - через UserDetailsService, - password encoder, - JWT verification, - LDAP, - OAuth2/OpenID Connect provider, - другой внешний источник identity.
Для username/password Spring обычно строит AuthenticationManager с DaoAuthenticationProvider. 
- Сохранение результата в
SecurityContextHolder
Если аутентификация успешна, в SecurityContextHolder сохраняется Authentication текущего пользователя.
После этого текущий principal доступен дальше по цепочке и в том же потоке.
После аутентификации идет авторизация, проверка: можно ли этому пользователю выполнять данный запрос.
AuthorizationFilter
Spring docs указывают, что AuthorizationFilter по умолчанию находится последним в security filter chain. Это означает, что перед authorization уже успевают выполниться authentication filters и exploit protections. 
Именно здесь проверяется: - authenticated ли пользователь, - есть ли у него нужная role/authority, - разрешен ли доступ к URL.
- Переход в Spring MVC
Если запрос прошел security filter chain: - он идет дальше в DispatcherServlet, - потом уже в HandlerMapping, - HandlerAdapter, - controller method.
То есть Spring Security стоит перед Spring MVC в servlet request pipeline.
Method Security — второй уровень защиты
После web-level authorization может включаться Method Security.
Это уже не filter chain, а защита вызовов методов:
- @PreAuthorize
- @PostAuthorize
- @Secured
- @RolesAllowed
Spring Security умеет защищать не только URL, но и вызовы методов.
Подключается обычно так:
После этого работают аннотации:
@PreAuthorize@PostAuthorize@PreFilter@PostFilter@Secured@RolesAllowed
Пример:
@PreAuthorize("hasRole('ADMIN') or #id == authentication.name")
public UserDto getUser(String id) { ... }
Под капотом это работает через Spring AOP / method interceptors:
Method invocation
-> Security interceptor
-> AuthorizationManager / expression evaluation
-> grant / deny
-> вызов метода или exception
Это важно, потому что URL-защиты недостаточно: сервис может вызываться не только из одного контроллера.
Абстракции Spring Security¶
Authentication — одна из центральных сущностей Spring Security. Она бывает в двух состояниях:
1. до успешной аутентификации - содержит credentials, но еще не trusted
2. после успешной аутентификации - содержит principal, authorities и признак authenticated
Authentication
principal -> кто пользователь
credentials -> пароль / токен / secret
authorities -> роли и права
details -> детали запроса
authenticated -> успешно ли подтверждена личность
Примеры реализаций:
- UsernamePasswordAuthenticationToken
- authenticated UsernamePasswordAuthenticationToken
- BearerTokenAuthenticationToken
- JwtAuthenticationProvider
- JwtAuthenticationToken
- AnonymousAuthenticationToken
- RememberMeAuthenticationToken
SecurityContext - хранит текущий Authentication.
Внутри него лежит:
- текущий Authentication
- а внутри Authentication:
- principal
- credentials
- authorities
- details
- флаг authenticated
SecurityContextHolder — это holder / доступ к текущему security context, это статическая точка доступа:
В servlet-приложении контекст обычно живет на время запроса и привязан к текущему потоку выполнения.¶
AuthenticationManager Это фасад. Он не обязан сам знать, как проверять все виды логина.
Обычно он делегирует работу в набор AuthenticationProvider.
Современная абстракция авторизации.
Она отвечает на вопрос: можно ли текущему Authentication получить доступ к этому ресурсу/методу?
Используется:
- в web security через AuthorizationFilter,
- в method security через interceptor'ы и annotations.
ProviderManager - Самая частая реализация AuthenticationManager.
Если provider умеет работать с данным типом Authentication, он пытается аутентифицировать.
Если получилось — возвращает authenticated object.
Если нет — управление идет дальше или выбрасывается исключение.
ProviderManager
-> Provider 1 supports this token? yes/no
-> Provider 2 supports this token? yes/no
-> Provider 3 supports this token? yes/no
¶
ProviderManager
-> Provider 1 supports this token? yes/no
-> Provider 2 supports this token? yes/no
-> Provider 3 supports this token? yes/no
AuthenticationProvider - Это конкретная стратегия проверки credentials.
- DaoAuthenticationProvider — username/password через UserDetailsService
- JWT/bearer provider — проверка токена
- LDAP provider — проверка через LDAP
- pre-auth provider — когда пользователя уже аутентифицировал внешний proxy/SSO
DaoAuthenticationProvider — самая типовая реализация для login/password. Его шаги:
1. получает username из входного token,
2. вызывает UserDetailsService,
3. получает UserDetails,
4. через PasswordEncoder сравнивает пароль,
5. проверяет флаги account locked/disabled/expired,
6. создает authenticated Authentication.
UserDetailsService - Используется чаще всего в username/password сценариях. Задача:
- найти пользователя в БД,
- вернуть его логин, хеш пароля, роли, флаги blocked/expired/locked.
Важно:
UserDetailsService не проверяет пароль сам. Он только загружает данные пользователя.
Проверка пароля делается в AuthenticationProvider через PasswordEncoder.
PasswordEncoder - Отвечает за безопасную работу с паролями. Основные правила:
- пароли не хранятся в открытом виде,
- сравнение идет через хеш,
- современный дефолт — bcrypt/argon2/pbkdf2/scrypt,
- NoOpPasswordEncoder — только для демо и тестов.
AuthorizationManager - Это стратегия принятия решения об авторизации. Она может учитывать:
- роли (ROLE_ADMIN)
- authorities (user:read, order:write)
- HTTP method
- URL path
- request matcher
- expression/rule
- объект вызова метода
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/orders/**").hasAuthority("order:read")
.anyRequest().authenticated()
);
¶
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/orders/**").hasAuthority("order:read")
.anyRequest().authenticated()
);
Модули Spring Security¶
| Модуль | Артефакт | Что внутри |
|---|---|---|
| Core | spring-security-core |
базовые интерфейсы: Authentication, GrantedAuthority, SecurityContext, AuthenticationManager, UserDetails, method security base |
| Web | spring-security-web |
servlet filters, FilterChainProxy, session/security context web infrastructure, CSRF, headers, request cache |
| Config | spring-security-config |
Java DSL (HttpSecurity), конфигурация SecurityFilterChain, @EnableWebSecurity, @EnableMethodSecurity |
| OAuth2 Core | spring-security-oauth2-core |
общие OAuth2 / OIDC модели и контракты |
| OAuth2 Client | spring-security-oauth2-client |
login через Google/GitHub/Keycloak, OAuth2 client flows |
| OAuth2 Resource Server | обычно через oauth2 modules + Boot autoconfig | JWT / opaque token validation для API |
| OAuth2 JOSE | spring-security-oauth2-jose |
JWS/JWE/JWT support, Nimbus integration |
| LDAP | spring-security-ldap |
интеграция с LDAP/Active Directory |
| ACL | spring-security-acl |
объектно-уровневые ACL-права, используется редко |
| Test | spring-security-test |
@WithMockUser, SecurityMockMvcRequestPostProcessors, helpers для тестов |
Обработка security-исключений¶
ExceptionTranslationFilter
Это не "фильтр аутентификации" и не "фильтр авторизации". Это bridge между Java exceptions и HTTP response.
Он ловит:
AuthenticationExceptionAccessDeniedException
И переводит их в корректное HTTP-поведение.
Если пользователь не аутентифицирован:
Когда возникает AuthenticationException:
ExceptionTranslationFilter
-> очищает SecurityContext
-> при необходимости сохраняет исходный запрос
-> вызывает AuthenticationEntryPoint
AuthenticationEntryPoint решает, как запросить credentials:
- redirect на login page,
- вернуть
401 Unauthorized, - добавить
WWW-Authenticate, - отдать JSON-ошибку.
Если пользователь аутентифицирован, но доступа нет:
Когда возникает AccessDeniedException:
| Компонент | Что делает |
|---|---|
ExceptionTranslationFilter |
переводит security exceptions в HTTP response |
AuthenticationEntryPoint |
запускает процесс логина / возвращает 401 |
AccessDeniedHandler |
обрабатывает 403 |
RequestCache |
может сохранить исходный запрос до логина |
SecurityContext в течение запроса¶
SecurityContext — это объект, который хранит информацию о текущей безопасности запроса/потока выполнения.
Жизненный цикл контекста
Начало запроса
-> создать/загрузить SecurityContext
-> положить в SecurityContextHolder
-> filters и приложение используют его
Конец запроса
-> сохранить при необходимости
-> очистить SecurityContextHolder
Почему это важно
Если контекст не очищать:
- один пользователь может "утечь" в другой запрос,
- особенно опасно при thread pools,
- возможны очень неприятные баги безопасности.
Поэтому FilterChainProxy и related filters не только ставят Authentication, но и обязаны правильно очищать контекст.
Где хранится context
Зависит от режима:
| Режим | Где обычно хранится |
|---|---|
| Stateful web app | session + request/thread context |
| Stateless JWT API | только на время текущего запроса |
| Test | часто вручную через test support |
Stateful и Stateless архитектура¶
Stateful. Типичный сценарий:
- form login,
- после логина есть session,
SecurityContextможет восстанавливаться между запросами,- удобно для server-side web приложений.
Схема:
Login request
-> authenticate
-> сохранить security state в session
Следующий request
-> достать context из session
-> пользователь уже считается вошедшим
Stateless. Типичный сценарий:
- REST API,
- JWT/Bearer token,
- session не используется как источник auth-state,
- каждый запрос сам приносит токен.
Схема:
Каждый request
-> Bearer token
-> token validation
-> создать Authentication
-> положить в SecurityContext только на время этого запроса
-> после ответа очистить
| Характеристика | Stateful | Stateless |
|---|---|---|
| Где хранится auth state | session | в токене / снаружи сервера |
| Нужна ли повторная аутентификация на каждом запросе | нет | да |
| Типичный UI | server-side web | SPA / mobile / public API |
| Масштабирование | сложнее | проще |
Фильтры Spring Security¶
| Filter | Роль |
|---|---|
SecurityContextHolderFilter |
подготавливает/управляет SecurityContextHolder на запросе |
HeaderWriterFilter |
security headers |
CorsFilter |
CORS |
CsrfFilter |
CSRF-защита |
LogoutFilter |
logout processing |
UsernamePasswordAuthenticationFilter |
form login / username-password authentication |
BasicAuthenticationFilter |
HTTP Basic |
BearerTokenAuthenticationFilter |
OAuth2 Resource Server / Bearer token |
AnonymousAuthenticationFilter |
создает anonymous authentication, если пользователя нет |
ExceptionTranslationFilter |
переводит security exceptions в HTTP response |
AuthorizationFilter |
проверка доступа |
Важно: порядок filters имеет значение. Например: - authentication должна произойти до authorization, - exception handling должен стоять так, чтобы поймать нужные исключения, - anonymous auth обычно ставится после реальной попытки аутентификации.