Search
Write a publication
Pull to refresh

Spring Security

Level of difficultyMedium
Reading time7 min
Views1.4K

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

Денис

Разработчик Java компании Programming store

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

Стандартная цепочка фильтров
Стандартная цепочка фильтров
  1. WebAsyncManagerIntegrationFilter — интеграция с асинхронными запросами.

  2. SecurityContextHolderFilter — сохранение информации аутентификации в SecurityContextHolder между запросами.

  3. HeaderWriterFilter — добавление HTTP‑заголовков безопасности (X‑Content‑Type‑Options, X‑Frame‑Options и др.).

  4. CsrfFilter — защита от CSRF‑атак (если включена).

  5. LogoutFilte — обработка выхода пользователя (/logout).

  6. UsernamePasswordAuthenticationFilter — аутентификация по логину и паролю (если включена форма входа).

  7. DefaultLoginPageGeneratingFilter — генерация страницы входа по умолчанию (если не настроена кастомная).

  8. DefaultLogoutPageGeneratingFilter — генерация страницы выхода по умолчанию.

  9. BasicAutenticationFilter — реализация базовой аутентификации HTTP (если используется HTTP Basic Auth)

  10. RequestCacheAwareFilter — сохранение запроса, требующего аутентификации (для редиректа после входа).

  11. SecurityContextHolderAwareRequestFilter — интеграция SecurityContext с сервлетами.

  12. AnonymousAuthenticationFilter — добавление анонимного пользователя, если никто не аутентифицирован.

  13. SessionManagementFilter — управление сессиями (например, ограничение количества сессий).

  14. ExceptionTranslationFilter — обработка исключений аутентификации и авторизации.

  15. FilterSecurityInterceptor — финальный фильтр, проверяющий доступ к ресурсам.

  16. 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 — подбор паролей или токенов.

Основные классы и интерфейсы Spring Security
Основные классы и интерфейсы Spring Security

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():

  1. Вернуть Authentication (с authenticated = true), если предполагается, что вход осуществляет корректный пользователь.

  2. Бросить AuthenticationException, если предполагается, что вход осуществляет некорректный пользователь.

  3.  Вернуть 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.

Аутентификация пользователя происходит следующим образом:

  1. Пользователь отправляет POST-запрос (например, на /login) с логином и паролем.

  2. Фильтр UsernamePasswordAuthenticationFilter сначала перехватывает POST-запрос (например, на /login), далее получает имя пользователя, а также его пароль и создает экземпляр класса UsernamePasswordAuthenticationToken (объект Authentication).

  3. Фильтр передает объект Authentication в AuthenticationManager для проверки. ProviderManager итерирует запрос аутентификации по этому списку AuthenticationProvider-ов, пока один из них не аутентифицирует пользователя.

  4. AuthenticationProvider проверяет учётные данные для логина/пароля. Это, например, DaoAuthenticationProvider. Далее загружает UserDetails через UserDetailsService => cравнивает пароли (используя PasswordEncoder) => если аутентификация успешна, возвращает Authentication с Authorities, если нет,  выбрасывает AuthenticationException.

  5. После успешной аутентификации SecurityContextHolder сохраняет Authentication в SecurityContext.

  6. Если аутентификация успешна, пользователь перенаправляется на запрашиваемый ресурс. Если нет, возвращается ошибка (401 Unauthorized) или перенаправляет на страницу логина.

Аутентификация пользователя
Аутентификация пользователя

Авторизация пользователя происходит следующим образом:

  1. Аутентифицированный пользователь отправляет HTTP-запрос к защищённому ресурсу.

  2. Поскольку у нас пользователь аутентифицированный, фильтры, связанные с аутентификацией (UsernamePasswordAuthenticationFilter, BasicAuthenticationFilter), сработали на этапе входа: восстановили пользователя из сессии и поместили информацию о нём (Authentication) в SecurityContext.

  3. Далее срабатывает AuthorizationFilter (фильтр авторизации) и использует AuthorizationManager для проверки доступа.

  4. AuthorizationManager получает текущего пользователя (Authentication) из SecurityContextHolder. Сопоставляет запрошенный путь (или метод) с правилами доступа, заданными в конфигурации (например, через .authorizeHttpRequests()). Если правило разрешает доступ => запрос попадает в контроллер или другой обработчик, если же нет, фильтр возвращает ошибку 403 Forbidden.

Авторизация пользователя
Авторизация пользователя
Tags:
Hubs:
+3
Comments1

Articles