HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-26
1 <p>Аутентификация - это проверка подлинности. Например, программа может проверить, действительно ли пользователь является тем, за кого себя выдает. Мы все участвуем в этом процессе, когда заполняем формы логинов и паролей. Spring Boot отвечает за реализацию этого процесса со стороны бэкенда.</p>
1 <p>Аутентификация - это проверка подлинности. Например, программа может проверить, действительно ли пользователь является тем, за кого себя выдает. Мы все участвуем в этом процессе, когда заполняем формы логинов и паролей. Spring Boot отвечает за реализацию этого процесса со стороны бэкенда.</p>
2 <p>В отличие от всех остальных эндпоинтов API, аутентификация в Spring Boot работает не сама по себе. Чтобы работать с ней, нам придется использовать достаточно навороченный пакет Spring Security. Этот пакет предоставляет множество готовых компонентов, но требует тонкой настройки. Это большая и сложная тема, по которой пишутся целые книги. Глубокое изучение Spring Security - это слишком сложно, и в этом курсе мы не будем погружаться в эту тему. Поэтому большую часть кода в этом уроке мы рассмотрим без подробного объяснения.</p>
2 <p>В отличие от всех остальных эндпоинтов API, аутентификация в Spring Boot работает не сама по себе. Чтобы работать с ней, нам придется использовать достаточно навороченный пакет Spring Security. Этот пакет предоставляет множество готовых компонентов, но требует тонкой настройки. Это большая и сложная тема, по которой пишутся целые книги. Глубокое изучение Spring Security - это слишком сложно, и в этом курсе мы не будем погружаться в эту тему. Поэтому большую часть кода в этом уроке мы рассмотрим без подробного объяснения.</p>
3 <p>Все события во время аутентификации можно разделить на два уровня:</p>
3 <p>Все события во время аутентификации можно разделить на два уровня:</p>
4 <ul><li><strong>Пользователь и его функциональность</strong>. Сюда входит все от регистрации до проверки доступов. Чтобы работать с этим уровнем, нужно взять представленный в Spring Boot набор интерфейсов и реализовать его - тогда часть нужной функциональности начнет работать автоматически</li>
4 <ul><li><strong>Пользователь и его функциональность</strong>. Сюда входит все от регистрации до проверки доступов. Чтобы работать с этим уровнем, нужно взять представленный в Spring Boot набор интерфейсов и реализовать его - тогда часть нужной функциональности начнет работать автоматически</li>
5 <li><strong>Конкретный способ аутентификации</strong>. В зависимости от способа аутентификации мы можем использовать разные механизмы и сторонние пакеты - например,<a>OAuth</a>или<a>JWT-токены</a></li>
5 <li><strong>Конкретный способ аутентификации</strong>. В зависимости от способа аутентификации мы можем использовать разные механизмы и сторонние пакеты - например,<a>OAuth</a>или<a>JWT-токены</a></li>
6 </ul><p>В этом уроке мы последовательно пройдем по всем частям системы и настроим их. Для аутентификации мы будем использовать JWT-токены. Во время логина система будет формировать JWT-токен, который вернется клиенту. Клиент будет пользоваться этим токеном для последующих запросов, иначе ему будет отказано в доступе.</p>
6 </ul><p>В этом уроке мы последовательно пройдем по всем частям системы и настроим их. Для аутентификации мы будем использовать JWT-токены. Во время логина система будет формировать JWT-токен, который вернется клиенту. Клиент будет пользоваться этим токеном для последующих запросов, иначе ему будет отказано в доступе.</p>
7 <h2>Установка зависимостей</h2>
7 <h2>Установка зависимостей</h2>
8 <p>Помимо Spring Security, нам понадобятся пакет для тестирования и пакет<em>oauth2-resource-server</em>, который выполняет большую часть логики по проверке доступа внутри себя:</p>
8 <p>Помимо Spring Security, нам понадобятся пакет для тестирования и пакет<em>oauth2-resource-server</em>, который выполняет большую часть логики по проверке доступа внутри себя:</p>
9 <h2>Настройка процесса аутентификации</h2>
9 <h2>Настройка процесса аутентификации</h2>
10 <p>Для аутентификации пользователя понадобится два поля:</p>
10 <p>Для аутентификации пользователя понадобится два поля:</p>
11 <ul><li><em>email</em>- это самый частый логин, кроме него иногда используют номер телефона или никнейм</li>
11 <ul><li><em>email</em>- это самый частый логин, кроме него иногда используют номер телефона или никнейм</li>
12 <li><em>passwordDigest</em>- это специальный хэш, связанный с паролем. Мы храним именно хэш, а не сам пароль, потому что с точки зрения безопасности, пароли хранить в базе данных нельзя. Чтобы не запутаться, мы назвали это поле<em>passwordDigest</em>, а не<em>password</em></li>
12 <li><em>passwordDigest</em>- это специальный хэш, связанный с паролем. Мы храним именно хэш, а не сам пароль, потому что с точки зрения безопасности, пароли хранить в базе данных нельзя. Чтобы не запутаться, мы назвали это поле<em>passwordDigest</em>, а не<em>password</em></li>
13 </ul><p>Внутри себя Spring Security работает с интерфейсом UserDetails, в который входят методы для работы с никнеймом, паролем и выдачей доступов. Сейчас мы реализуем только аутентификацию, поэтому нас интересует только часть методов. Остальные методы мы тоже реализуем, но после базовой функциональности:</p>
13 </ul><p>Внутри себя Spring Security работает с интерфейсом UserDetails, в который входят методы для работы с никнеймом, паролем и выдачей доступов. Сейчас мы реализуем только аутентификацию, поэтому нас интересует только часть методов. Остальные методы мы тоже реализуем, но после базовой функциональности:</p>
14 <p>Обычно Spring Security работает с username, но под ним может скрываться что-то другое. Например, в нашем случае геттер возвращает email. Кроме того, вместо пароля мы получаем passwordDigest. Здесь все тоже корректно, потому что сравнение будет происходить с хэшем, а не с введенным пользователем паролем.</p>
14 <p>Обычно Spring Security работает с username, но под ним может скрываться что-то другое. Например, в нашем случае геттер возвращает email. Кроме того, вместо пароля мы получаем passwordDigest. Здесь все тоже корректно, потому что сравнение будет происходить с хэшем, а не с введенным пользователем паролем.</p>
15 <p>Далее мы создадим сервис, реализующий интерфейс UserDetailsManager. Через него Spring Boot будет выполнять CRUD-операции над пользователем. Для аутентификации реализуем метод loadUserByUsername():</p>
15 <p>Далее мы создадим сервис, реализующий интерфейс UserDetailsManager. Через него Spring Boot будет выполнять CRUD-операции над пользователем. Для аутентификации реализуем метод loadUserByUsername():</p>
16 <p>Дальше нам понадобится механизм хеширования пароля. Чтобы работать с ним, создадим бин:</p>
16 <p>Дальше нам понадобится механизм хеширования пароля. Чтобы работать с ним, создадим бин:</p>
17 <p>Теперь собираем все вместе. Указываем, что Spring Security должен использовать наши компоненты:</p>
17 <p>Теперь собираем все вместе. Указываем, что Spring Security должен использовать наши компоненты:</p>
18 <p>Мы сделали почти всю подготовительную работу. Осталось создать утилиту для генерации JWT-токена, и мы будем готовы реализовать аутентификацию.</p>
18 <p>Мы сделали почти всю подготовительную работу. Осталось создать утилиту для генерации JWT-токена, и мы будем готовы реализовать аутентификацию.</p>
19 <p>Генерация состоит из двух вещей - подготовки токена и его шифрования. Рассмотрим пример класса, который делает эти операции:</p>
19 <p>Генерация состоит из двух вещей - подготовки токена и его шифрования. Рассмотрим пример класса, который делает эти операции:</p>
20 <p>В коде выше используется JwtEncoder. Он добавляется в класс EncodersConfig, в который мы уже добавили PasswordEncoder:</p>
20 <p>В коде выше используется JwtEncoder. Он добавляется в класс EncodersConfig, в который мы уже добавили PasswordEncoder:</p>
21 <p>Для работы JwtEncoderнужны RSA-ключи - их можно сгенерировать, выполнив в терминале команды:</p>
21 <p>Для работы JwtEncoderнужны RSA-ключи - их можно сгенерировать, выполнив в терминале команды:</p>
22 <p>Затем мы должны зайти в<em>application.yml</em>и указать путь к ключам. Например, вот так:</p>
22 <p>Затем мы должны зайти в<em>application.yml</em>и указать путь к ключам. Например, вот так:</p>
23 <p>Дальше мы создаем компонент, который прочитает эти ключи и сделает их доступными в коде:</p>
23 <p>Дальше мы создаем компонент, который прочитает эти ключи и сделает их доступными в коде:</p>
24 <p>Не забудьте, что в реальных проектах хранить приватный ключ в репозитории нельзя. Доступ к приватному ключу должен быть ограничен.</p>
24 <p>Не забудьте, что в реальных проектах хранить приватный ключ в репозитории нельзя. Доступ к приватному ключу должен быть ограничен.</p>
25 <p>В нашем случае аутентификация - это обычный POST-запрос, в котором передаются электронная почта и пароль. Для унификации с предыдущими настройками, мы будем использовать имя username вместо почты email. В итоге у нас получится такой DTO:</p>
25 <p>В нашем случае аутентификация - это обычный POST-запрос, в котором передаются электронная почта и пароль. Для унификации с предыдущими настройками, мы будем использовать имя username вместо почты email. В итоге у нас получится такой DTO:</p>
26 <p>Наконец, перейдем к контроллеру:</p>
26 <p>Наконец, перейдем к контроллеру:</p>
27 <p>Все проделанные шаги свелись к строчке authenticationManager.authenticate(authentication). В итоге внутри кода выполняется поиск пользователя в базе данных, хэширование пароля и сравнение. Если аутентификация прошла, выполнение продолжается дальше, если нет - код выбрасывает исключение.</p>
27 <p>Все проделанные шаги свелись к строчке authenticationManager.authenticate(authentication). В итоге внутри кода выполняется поиск пользователя в базе данных, хэширование пароля и сравнение. Если аутентификация прошла, выполнение продолжается дальше, если нет - код выбрасывает исключение.</p>
28 <p>После аутентификации программа генерирует токен и возвращает его клиенту. С этого момента клиент сам следит за отправкой токена в последующих запросах к API. При этом мы должны убрать API из публичного доступа и включить проверку токена.</p>
28 <p>После аутентификации программа генерирует токен и возвращает его клиенту. С этого момента клиент сам следит за отправкой токена в последующих запросах к API. При этом мы должны убрать API из публичного доступа и включить проверку токена.</p>
29 <h2>Проверка наличия аутентификации</h2>
29 <h2>Проверка наличия аутентификации</h2>
30 <p>Проверка аутентификации настраивается в конфигурации, помеченной аннотацией @EnableWebSecurity. Выше мы уже создали такой класс, добавив туда конфигурацию провайдера и менеджера аутентификации. Теперь добавим туда фильтр, который применяется ко всем входящим запросам:</p>
30 <p>Проверка аутентификации настраивается в конфигурации, помеченной аннотацией @EnableWebSecurity. Выше мы уже создали такой класс, добавив туда конфигурацию провайдера и менеджера аутентификации. Теперь добавим туда фильтр, который применяется ко всем входящим запросам:</p>
31 <h2>Извлечение текущего пользователя</h2>
31 <h2>Извлечение текущего пользователя</h2>
32 <p>Кроме проверки доступа, в коде может понадобиться пользователь, выполняющий запрос. Например, такое бывает нужно, когда создается какая-то сущность, у которой есть автор. В таком случае автор почти всегда тот, кто выполняет запрос. Spring Security предоставляет возможность извлечь его из контекста:</p>
32 <p>Кроме проверки доступа, в коде может понадобиться пользователь, выполняющий запрос. Например, такое бывает нужно, когда создается какая-то сущность, у которой есть автор. В таком случае автор почти всегда тот, кто выполняет запрос. Spring Security предоставляет возможность извлечь его из контекста:</p>
33 <p>Этот класс позволяет получать пользователя в контроллере одним вызовом.</p>
33 <p>Этот класс позволяет получать пользователя в контроллере одним вызовом.</p>