2022-10-12 09:16:56
Spring Security: основная архитектура
Времена сложные, но надеюсь вернуться в стабильное написание постов.
У многих разработчиков Spring Security — самый непонятный и нелюбимый модуль. Секрет успеха в работе с Security лежит в понимании двух вещей:
Используемый механизм безопасности
Вариантов и комбинаций много — БД, LDAP, JWT, разные схемы oAuth и SSO. При реализации должно быть чёткое понимание, что где хранится, куда передаётся, кто, как и что валидирует
Базовая архитектура Spring Security
Об этом и будет пост. Опишу простыми словами, что происходит в классической (не реактивной) архитектуре. Надеюсь, это поможет увидеть общую картину.
Глобально сфера Security делится на две большие части:
Путь запроса до контроллера и обратно
Схема такая:
Сервер получает запрос
Проводит его через цепочку фильтров, у каждого из которых своя задача:
CsrfFilter проверяет CSRF токен
SessionManagementFilter разбирается с сессией
И так далее, всего около 10-15 фильтров (количество зависит от конфига)
Запрос попадает в контроллер
Выполняется специфичная логика проекта
Контроллер возвращает ответ
Запрос проходит по той же цепочке фильтров, но в обратном порядке. Фильтры выставят у ответа нужные заголовки, обнулят/сохранят сессию и тд
Ответ возвращается пользователю
Что тут важно:
Проверка прав, заголовков и основные секьюрити штуки обрабатываются в фильтрах ДО вызова контроллера
Каждый фильтр может менять запрос/ответ или контекст, поэтому порядок фильтров имеет значение
За конфигурацию фильтров отвечает метод конфига WebSecurityConfigurerAdapter#configure(HttpSecurity http)
Чтобы увидеть процесс наглядно, поставьте брейкпойнт в методе FilterChainProxy#doFilterInternal. Там увидите все фильтры, можете погулять по ним и отследить, что происходит с запросом
Авторизация/аутентификация
скрывается за фасадом — бином AuthenticationManager.
Чтобы авторизовать пользователя, в коде проекта нужно вызвать метод
authenticationManager.authenticate
Может в фильтре, может в контроллере, зависит от механизма безопасности.
Метод authenticate пройдётся по всем заданным источникам авторизации. Результат можно положить в SecurityContextHolder, и он будет доступен из любого места в коде. Также бросится событие AuthenticationSuccessEvent, но обычно его игнорируют:)
Источники авторизации называются *Provider (например, DaoAuthenticationProvider) и определяются в конфиге. В итоге всё что нужно от разработчика это:
определить провайдеры
вызвать authenticate в нужном месте
Посмотреть список текущих провайдеров: брейкпойнт в методе ProviderManager#authenticate.
Задать список провайдеров: определить в конфиге WebSecurityConfigurerAdapter метод configure(AuthenticationManagerBuilder auth)
Что с этим знанием делать на практике
Если вам дали задачу на настройку Spring Security, не бросайтесь сразу гуглить
spring security jwt/oauth/ldap example
Велик шанс сделать что-нибудь не то. Лучше сделать так:
Шаг 1 (самый важный): разобраться в механизме безопасности, который нужно реализовать. Что, откуда и как передаётся, где и как валидируется и сохраняется
Шаг 2: прикинуть, как эти требования ложатся на архитектуру секьюрити. Что сделать в фильтрах, а что отдельным методом
Шаг 3 (желательный): обсудить полученное решение со старшим товарищем
Теперь можно гуглить и выбрать подходящий туториал. Поняв, что именно искать, вы в итоге потратите меньше времени и сохраните высокую самооценку
1 view06:16