«В интернете есть множество статей про данный фреймворк Spring, но хочется рассказать о нем простыми словами, без сложных фраз, чтобы любой новичок мог легко разобраться», — рассказывает мой коллега Денис.

Денис
Разработчик Java компании Programming store
Spring Security — это мощный и важный фреймворк в Spring для обеспечения безопасности приложения при помощи аутентификации и авторизации. Основан на цепочке фильтров. Например, в Spring Security 6.5.1. по умолчанию стандартная цепочка фильтров (FilterChainProxy) содержит до 12–16 фильтров, выполняющих различные механизмы проверки. Стоит отметить, что точное количество фильтров зависит от конфигурации.

WebAsyncManagerIntegrationFilter — интеграция с асинхронными запросами.
SecurityContextHolderFilter — сохранение информации аутентификации в SecurityContextHolder между запросами.
HeaderWriterFilter — добавление HTTP‑заголовков безопасности (X‑Content‑Type‑Options, X‑Frame‑Options и др.).
CsrfFilter — защита от CSRF‑атак (если включена).
LogoutFilte — обработка выхода пользователя (/logout).
UsernamePasswordAuthenticationFilter — аутентификация по логину и паролю (если включена форма входа).
DefaultLoginPageGeneratingFilter — генерация страницы входа по умолчанию (если не настроена кастомная).
DefaultLogoutPageGeneratingFilter — генерация страницы выхода по умолчанию.
BasicAutenticationFilter — реализация базовой аутентификации HTTP (если используется HTTP Basic Auth)
RequestCacheAwareFilter — сохранение запроса, требующего аутентификации (для редиректа после входа).
SecurityContextHolderAwareRequestFilter — интеграция SecurityContext с сервлетами.
AnonymousAuthenticationFilter — добавление анонимного пользователя, если никто не аутентифицирован.
SessionManagementFilter — управление сессиями (например, ограничение количества сессий).
ExceptionTranslationFilter — обработка исключений аутентификации и авторизации.
FilterSecurityInterceptor — финальный фильтр, проверяющий доступ к ресурсам.
AuthorizationFilter (появился в 6 версии) — замена часть логики в FilterSecurityInterceptor.
Аутентификация — процесс проверки пользователя (логин‑пароль).
Авторизация — процесс проверки того, что можно делать аутентифицированному пользователю на основе ролей (просмотр определенных страниц и разделов сайта).
Данный фреймворк также позволяет защитить наше приложение от несанкционированных атак, таких как XSS, CSRF, Session Fixation, SQL‑инъекций, Clickjacking, Brute Force и др.
XSS (Cross‑Site Scripting) — атака с использованием межсайтового скриптинга, т. е. злоумышленник может через внедрение кода в браузере или через внедрение кода на сервере получить доступ к содержимому всех страниц и выполнять действия (например, http‑запросы) под учетными записями пользователей.
CSRF (Cross‑Site Request Forgery) — атака, при которой злоумышленник заставляет пользователя выполнить нежелательное действие на сайте, на котором тот уже аутентифицирован, т. е. межсайтовая подделка запроса.
Session Fixation — атака, при которой злоумышленник фиксирует ID сессии жертвы.
SQL‑инъекции — внедрение вредоносного SQL‑кода в запросы.
Clickjacking — подмена интерфейса, когда пользователь думает, что кликает на одном сайте, а на самом деле взаимодействует с другим.
Brute Force — подбор паролей или токенов.

SecurityContextHolder — класс, где хранится информация о том, кто аутентифицирован (подробная информация о пользователе). Также это класс, хранящий по умолчанию в ThreadLocal переменной SecurityContext‑ы (текущие контексты безопасности) для каждого потока и содержащий статические методы для работы с SecurityContext‑ами, а через них с текущим объектом Authentication, привязанным к нашему веб‑запросу.
SecurityContext — интерфейс, отражающий контекст безопасности для текущего потока. Содержит объект Authentication (аналог ApplicationContext, в котором находятся бины). По умолчанию на каждый поток создается один SecurityContext. Имеет два метода — getAuthentication() и setAuthentication(Authentication authentication).
Authentication — интерфейс, содержащий информацию о текущем пользователе и его разрешениях (Principal). Работа Spring Security заключается в том, что различные фильтры и обработчики будут брать и класть объект Authentication для каждого пользователя. Authentication имеет реализацию по умолчанию — класс UsernamePasswordAuthenticationToken, предназначенный для хранения логина, пароля и коллекции Authorities. В Authentication есть метод getPrincipal(), возвращающий Object. При аутентификации с использованием имени пользователя/пароля Principal реализуется объектом типа UserDetails.
Principal — интерфейс из пакета java.security, отражающий учетную запись пользователя. Это логин.
Credentials — любой Object; то, что подтверждает учетную запись пользователя. Это пароль.
Authorities — права доступа (роли и разрешения), которые назначаются пользователю и определяют, что может делать пользователь в приложении. Authorities хранятся в Authentication и проверяются Spring Security.
GrantedAuthority — интерфейс, который описывает одно право пользователя в Spring Security. Обычно используется через реализацию SimpleGrantedAuthority. Иными словами, это полномочия, представленные пользователю (уровни доступа или роли).
UserDetails — интерфейс, представляющий учетную запись пользователя. UserDetails выступает в качестве Principal, т. е. как представление текущего аутентифицированного пользователя.
Как правило, модель пользователя должна имплементировать его. Она просто хранит пользовательскую информацию в виде логина, пароля и флагов isAccountNonExpired, isAccountNonLocked, isCredentialsNonExpired, isEnabled, а также коллекции прав (ролей) пользователя. Данная информация позже инкапсулируется в объекты Authentication в случае успешной авторизации.
UserDetailsService — интерфейс объекта, реализующего загрузку пользовательских данных из хранилища. Созданный нами объект с этим интерфейсом должен обращаться к БД и получать оттуда юзеров. Единственный метод этого интерфейса принимает имя пользователя в виде String и возвращает UserDetails. Он представляет собой Principal, но в расширенном виде и с учетом специфики приложения.
AuthenticationManager — основной стратегический интерфейс для аутентификации. Имеет только один метод authenticate(), который срабатывает, когда пользователь пытается аутентифицироваться в системе.
AuthenticationManager может сделать одну из 3 вещей в своем методе authenticate():
Вернуть Authentication (с authenticated = true), если предполагается, что вход осуществляет корректный пользователь.
Бросить AuthenticationException, если предполагается, что вход осуществляет некорректный пользователь.
Вернуть null, если принять решение не представляется возможным.
ProviderManager — класс, который реализует AuthenticationManager, содержит поле private List<AuthenticationProvider> providers со списком AuthenticationProvider‑ов и итерирует запрос аутентификации по этому списку AuthenticationProvider‑ов. По очереди передает запрос каждому провайдеру из списка, пока один из них не сможет обработать запрос. Такое разделение позволяет поддерживать в одном приложении разные механизмы аутентификации (через БД, токены, LDAP, OAuth2.0).
AuthenticationProvider — интерфейс, определяющий объект, выполняющий аутентификацию. Он имеет множество готовых реализаций:
DaoAuthenticationProvider — аутентификация через UserDetailsService (например, БД),
LdapAuthenticationProvider — аутентификация через LDAP,
JwtAuthenticationProvider — аутентификация через JWT,
OAuth2AuthenticationProvider — аутентификация через OAuth2.0.
Также можно реализовать свой AuthenticationProvider. В небольших проектах обычно используется один провайдер (например, для аутентификации по логину и паролю), а в более крупных проектах – несколько провайдеров (например через Google, OAuth2, LDAP и т.д.), для каждого из которых создается свой объект AuthenticationProvider.
Интерфейс AuthenticationProvider похож на AuthenticationManager, но дополнительно содержит метод supports(Class<?> authentication), который позволяет определить, поддерживает ли данный провайдер конкретный тип объекта Authentication. Это позволяет гибко настраивать разные механизмы аутентификации в одном приложении.
PasswordEncoder – интерфейс для шифрования/расшифровывания паролей. Одна из популярных реализаций – BCryptPasswordEncoder.
Аутентификация пользователя происходит следующим образом:
Пользователь отправляет POST-запрос (например, на /login) с логином и паролем.
Фильтр UsernamePasswordAuthenticationFilter сначала перехватывает POST-запрос (например, на /login), далее получает имя пользователя, а также его пароль и создает экземпляр класса UsernamePasswordAuthenticationToken (объект Authentication).
Фильтр передает объект Authentication в AuthenticationManager для проверки. ProviderManager итерирует запрос аутентификации по этому списку AuthenticationProvider-ов, пока один из них не аутентифицирует пользователя.
AuthenticationProvider проверяет учётные данные для логина/пароля. Это, например, DaoAuthenticationProvider. Далее загружает UserDetails через UserDetailsService => cравнивает пароли (используя PasswordEncoder) => если аутентификация успешна, возвращает Authentication с Authorities, если нет, выбрасывает AuthenticationException.
После успешной аутентификации SecurityContextHolder сохраняет Authentication в SecurityContext.
Если аутентификация успешна, пользователь перенаправляется на запрашиваемый ресурс. Если нет, возвращается ошибка (401 Unauthorized) или перенаправляет на страницу логина.

Авторизация пользователя происходит следующим образом:
Аутентифицированный пользователь отправляет HTTP-запрос к защищённому ресурсу.
Поскольку у нас пользователь аутентифицированный, фильтры, связанные с аутентификацией (UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter), сработали на этапе входа: восстановили пользователя из сессии и поместили информацию о нём (Authentication) в SecurityContext.
Далее срабатывает AuthorizationFilter (фильтр авторизации) и использует AuthorizationManager для проверки доступа.
AuthorizationManager получает текущего пользователя (Authentication) из SecurityContextHolder. Сопоставляет запрошенный путь (или метод) с правилами доступа, заданными в конфигурации (например, через .authorizeHttpRequests()). Если правило разрешает доступ => запрос попадает в контроллер или другой обработчик, если же нет, фильтр возвращает ошибку 403 Forbidden.
