Spring Security 6. Авторизация и аутентификация на основе Базы данных

Привет. Меня зовут Кирилл, я Java-разработчик. Несмотря на то, что уже год я работаю в компании, по-прежнему стараюсь находить время для собственных проектов, с помощью которых осваиваю интересующие меня технологии и подходы. Именно на таком проекте я решил разобраться, как работает авторизация и аутентификация на основе базы данных в Spring Security 6. Изменений, по сравнению с предыдущими версиями немало. Примеры из документации не отвечают на все вопросы в полной мере, а материалов на русском языке по этой теме я, как ни старался, не нашел. Информацию собирал из разных иностранных источников по кусочкам. Теперь поделюсь с вами найденным.

Думаю, что большая часть читателей знают, что такое авторизация и аутентификация, а также чем они отличаются друг от друга. Но на случай, если статью будут читать ученики, грубо обобщу, что авторизация — это отсечение тех, кто не имеет в целом допуска к ресурсу. Аутентификация — распределение возможностей (прав, допусков) для авторизованных пользователей. Аутентификация осуществляется на основе ролей зарегистрированного пользователя и других свойств, которых коснемся позже.

Основная проблема заключается в том, что с версии Spring Security 5.7.0 класс WebSecurityConfigurerAdapter признан устаревшим и в дальнейших версиях его использование невозможно. Большинство же существующих на данный момент гайдов опираются на наследование этого класса.

В своем материале я предложу вашему вниманию минимальный скелет сервиса с авторизацией и аутентификацией на основе базы данных, с нескольким эндпоинтами, допуски к которым регулируются ролями и уровнями доступа. Скелет будет по-настоящему минимальным, чтобы не перегружать читателя лишней информацией и логикой, при этом полностью познакомить с концепцией. В дальнейшем вы можете расширить этот скелет, добавив свою функциональность и приспособив под свои задачи.

Итак, для начала создадим новый проект через Spring Initializr.

Создание проекта

Создание проекта

Здесь все стандартно. Выбираем любую современную версию Spring, сборка через Maven, ну и прописываем метаданные. Дополнительно подключаем Spring Web, Spring Data JPA, MySQL Driver (в примере я буду использовать MySQL, а вы можете использовать любую б/д на усмотрение) и Lombok (мне с ним удобнее).

Далее создадим базу данных с необходимыми нам полями. Обязательные для нашего проекта поля — name, password и role. В колонку с паролем пишем какой-то простой пароль (для более лёгкого запоминания и тестирования), закодированный в Bcrypt password encoder. У меня для всех 1234, закодированные со сложностью 5 (это надо запомнить и в дальнейшем применить, иначе авторизация не сработает).

Моя база для теста выглядит так

Моя база для теста выглядит так

Открываем проект и прописываем нашу базу в Application Properties.

0670887a08f51cdf3d9f17b94ae02717.png

Затем я люблю запуститься и проверить, всё ли работает. Поскольку мы пока не написали класс SecurityConfig, зайдем по имени user, а пароль берём из логов. 

Теперь напишем сущность User, с помощью которой будем раскладывать пользователей из базы в объекты, а также интерфейс UserRepository, расширяющий JpaRepository…

Entity User

Entity User

UserRepo

UserRepo

Далее создадим класс TestController, где пропишем несколько эндпоинтов, к которым можно обратиться согласно уровню доступа пользователей:

  • welcome — страница будет показываться всем, даже без прохождения авторизации;

  • users — страница для тех, кто имеет роль USER в базе данных;

  • admins — для всех админов;

  • all — для всех, но после авторизации.

Простейший контроллер с распределением эндпоинтов по ролям

Простейший контроллер с распределением эндпоинтов по ролям

Сервисов с развернутой бизнес логикой в нашем примере не будет, поскольку, как я упоминал раньше, мы пишем простой, минимальный скелет, поэтому не вижу смысла перегружать способности понимания читателей.

Чтобы настроить работу SpringSecurity, создадим класс SecurityConfig, где опишем принципы авторизации и аутентификации для каждого эндпоинта, зададим UserDetailService, PasswordEncoder и остальные свойства.

Самый важный класс при работе со Spring Security

Самый важный класс при работе со Spring Security

Пишем кастомный MyUserDetailsService, расширяющий UserDetailService и реализующий метод loadUserByUsername…

9004bc5ab16495d926a7f590b6e4cf4f.png

… и связанный с ним кастомный MyUserDetails, который переводит нашего «юзера» из базы данных в «юзера», понятного SpringSecurity. Здесь мы расширяем стандартный UserDetails и переопределяем все его методы.

Метод GetAuthorities написан так на случай, если у кого-то из базы будет несколько ролей

Метод GetAuthorities написан так на случай, если у кого-то из базы будет несколько ролей

Запускаем проект и тестируем эндпоинты. В результате, на страницу «welcome» вход осуществляется без авторизации, на страницу «all» могут зайти все зарегистрированные пользователи, а доступ к страницам «users» и «admins» имеют соответственно пользователи с ролями USER и ADMIN. 

Как я говорил ранее, в дальнейшем функциональность скелета можно расширить, навесить на него бизнес логику, добавить и сделать более интересные эндпоинты, усложнить базу юзеров и поменять архитектуру SecurityFilterChain. Эту основу несложно вшить и в любой существующий проект, немного доработав под свои свои условия. (Если будет интересно — покажу, как встроил его в свой проект электронной библиотеки).

Если у вас возникнут вопросы, буду рад ответить на них в комментариях. Также буду рад обратной связи от более матерых и искушенных разработчиков.

Репозиторий проекта здесь.

© Habrahabr.ru