Spring Boot
Теория: Аутентификация
Аутентификация — это проверка подлинности. Например, программа может проверить, действительно ли пользователь является тем, за кого себя выдает. Мы все участвуем в этом процессе, когда заполняем формы логинов и паролей. Spring Boot отвечает за реализацию этого процесса со стороны бэкенда.
В отличие от всех остальных эндпоинтов API, аутентификация в Spring Boot работает не сама по себе. Чтобы работать с ней, нам придется использовать достаточно навороченный пакет Spring Security. Этот пакет предоставляет множество готовых компонентов, но требует тонкой настройки. Это большая и сложная тема, по которой пишутся целые книги. Глубокое изучение Spring Security — это слишком сложно, и в этом курсе мы не будем погружаться в эту тему. Поэтому большую часть кода в этом уроке мы рассмотрим без подробного объяснения.
Все события во время аутентификации можно разделить на два уровня:
- Пользователь и его функциональность. Сюда входит все от регистрации до проверки доступов. Чтобы работать с этим уровнем, нужно взять представленный в Spring Boot набор интерфейсов и реализовать его — тогда часть нужной функциональности начнет работать автоматически
- Конкретный способ аутентификации. В зависимости от способа аутентификации мы можем использовать разные механизмы и сторонние пакеты — например, OAuth или JWT-токены
В этом уроке мы последовательно пройдем по всем частям системы и настроим их. Для аутентификации мы будем использовать JWT-токены. Во время логина система будет формировать JWT-токен, который вернется клиенту. Клиент будет пользоваться этим токеном для последующих запросов, иначе ему будет отказано в доступе.
Установка зависимостей
Помимо Spring Security, нам понадобятся пакет для тестирования и пакет oauth2-resource-server, который выполняет большую часть логики по проверке доступа внутри себя:
Настройка процесса аутентификации
Для аутентификации пользователя понадобится два поля:
- email — это самый частый логин, кроме него иногда используют номер телефона или никнейм
- passwordDigest — это специальный хэш, связанный с паролем. Мы храним именно хэш, а не сам пароль, потому что с точки зрения безопасности, пароли хранить в базе данных нельзя. Чтобы не запутаться, мы назвали это поле passwordDigest, а не password
Внутри себя Spring Security работает с интерфейсом UserDetails, в который входят методы для работы с никнеймом, паролем и выдачей доступов. Сейчас мы реализуем только аутентификацию, поэтому нас интересует только часть методов. Остальные методы мы тоже реализуем, но после базовой функциональности:
Обычно Spring Security работает с username, но под ним может скрываться что-то другое. Например, в нашем случае геттер возвращает email. Кроме того, вместо пароля мы получаем passwordDigest. Здесь все тоже корректно, потому что сравнение будет происходить с хэшем, а не с введенным пользователем паролем.
Далее мы создадим сервис, реализующий интерфейс UserDetailsManager. Через него Spring Boot будет выполнять CRUD-операции над пользователем. Для аутентификации реализуем метод loadUserByUsername():
Дальше нам понадобится механизм хеширования пароля. Чтобы работать с ним, создадим бин:
Теперь собираем все вместе. Указываем, что Spring Security должен использовать наши компоненты:
Мы сделали почти всю подготовительную работу. Осталось создать утилиту для генерации JWT-токена, и мы будем готовы реализовать аутентификацию.
Генерация состоит из двух вещей — подготовки токена и его шифрования. Рассмотрим пример класса, который делает эти операции:
В коде выше используется JwtEncoder. Он добавляется в класс EncodersConfig, в который мы уже добавили PasswordEncoder:
Для работы JwtEncoderнужны RSA-ключи — их можно сгенерировать, выполнив в терминале команды:
Затем мы должны зайти в application.yml и указать путь к ключам. Например, вот так:
Дальше мы создаем компонент, который прочитает эти ключи и сделает их доступными в коде:
Не забудьте, что в реальных проектах хранить приватный ключ в репозитории нельзя. Доступ к приватному ключу должен быть ограничен.
В нашем случае аутентификация — это обычный POST-запрос, в котором передаются электронная почта и пароль. Для унификации с предыдущими настройками, мы будем использовать имя username вместо почты email. В итоге у нас получится такой DTO:
Наконец, перейдем к контроллеру:
Все проделанные шаги свелись к строчке authenticationManager.authenticate(authentication). В итоге внутри кода выполняется поиск пользователя в базе данных, хэширование пароля и сравнение. Если аутентификация прошла, выполнение продолжается дальше, если нет — код выбрасывает исключение.
После аутентификации программа генерирует токен и возвращает его клиенту. С этого момента клиент сам следит за отправкой токена в последующих запросах к API. При этом мы должны убрать API из публичного доступа и включить проверку токена.
Проверка наличия аутентификации
Проверка аутентификации настраивается в конфигурации, помеченной аннотацией @EnableWebSecurity. Выше мы уже создали такой класс, добавив туда конфигурацию провайдера и менеджера аутентификации. Теперь добавим туда фильтр, который применяется ко всем входящим запросам:
Извлечение текущего пользователя
Кроме проверки доступа, в коде может понадобиться пользователь, выполняющий запрос. Например, такое бывает нужно, когда создается какая-то сущность, у которой есть автор. В таком случае автор почти всегда тот, кто выполняет запрос. Spring Security предоставляет возможность извлечь его из контекста:
Этот класс позволяет получать пользователя в контроллере одним вызовом.



