Доброе время!
Часть 2-я по открытому занятию нового учебного курса: реализация простого JWT через новый Spring Boot OAuth2 Resource Server (первая часть: Spring Boot 3.0 — готовимся заранее). Что такое JWT и зачем, писать здесь не буду — в сети материалов много, начинать знакомство обычно рекомендую с Википедии. А вот хорошая ссылка по реализации JWT+OAuth2. Здесь я привожу Java код, основанный на официальном примере spring-projects — простейшей реализации JWT Login Sample (без refresh token и отдельного авторизационного сервера), «творчески доработанный» и с моими пояснениями. Еще раз — без теории, для тех, кому интересен код актуальной Java реализации. Если это Вы — прошу к прочтению.

Чтобы не повторять каждый раз основы, курс стартует с финального кода открытого проекта Spring Boot 2.x + HATEOAS: Basic аутентификация и авторизация на основе ролей, регистрация пользователя в приложении, управление своим профилем и администрирование пользователей (код на GitHub). Реализация добавления JWT аутентификации находится в ветке patched другого репозитория, начиная с 3го комита. Для разбора кода проще всего вычекать его к себе:
git clone --branch patched https://github.com/JavaOPs/cloudjava
Комит 1_03_jwt_token: реализуем получение JWT токена
Для большей безопасности будем использовать ассиметричные RS256 ключи: проверить подлинность токена может любой, а подписать его только тот, у кого есть приватный ключ. Ключи можно взять мои или заменить их своими (generate PKCS#8 private key with openssl):
openssl genpkey -out jwt.pem -algorithm RSA -pkeyopt rsa_keygen_bits:2048 - приватный ключ в формате PKCS8писи openssl rsa -in jwt.pem -pubout -out jwt.pub - публичный ключ для декодирования
-
Поместим ключи в
\src\main\resources\keys -
Добавляем зависимость
org.springframework.boot:spring-boot-starter-oauth2-resource-server -
Объявляем ключи в
application.yamlи инжектим вJwtSecurityConfiguration. С помощью ключей создаем биныJwtDecoder/JwtEncoder -
SecurityConfigurationпереименовываем вLoginSecurityConfiguration.httpBasicбудем использовать только для получения JWT -
Создаем
TokenController, где генерируем JWT. Детали залогиненного пользователяAuthUserдобавляю в JWT в отдельномJwtUtil#addUserDetails -
«Выключаем» тесты через Junit5
@Disabled
Перед сборкой не забываем gradle clean. Проверяем получение JWT:
### JetBrains Tools->HTTP Client POST http://localhost:8080/token Authorization: Basic admin@javaops.ru admin
В ответ получаем длинную строке типа eyJhbGciOiJSUzI1NiJ9...Она хорошо копируется из JetBrains HTTP Client. В Unix можно сделать export, который можно использовать после следующего комита:
export JWT=`curl -XPOST -u admin@javaops.ru:admin localhost:8080/token`
1_04_jwt_api: переводим API на JWT:
-
Добавляем
JwtUser, которого будем получать после JWT аутентификации изJwtUtil#createJwtUser. Функционал по добавлению деталей пользователя в JWT и извлечению их должен находиться рядом. -
Добавляем в
JwtSecurityConfigurationвторую конфигурацию AAA по JWT (порядок фильтров не важен). Конвертируем стандартныйJwtAuthenticationTokenвJwtUser. -
В методы
ProfileControllerинжектим наш principal objectJwtUser(с@AuthenticationPrincipalу меня не работает). В случае, когда для функционала необходим пользователь, достаем его из базы:AbstractUserController#findByJwtUser(не обязательно это делать для всех методов API). -
В SecurityContext при обращении к API теперь попадает объект
JwtUser, рефакторимSecurityUtil
gradle clean и проверяем API: полученный JWT вставляем вместо [JWT_TOKEN]:
GET http://localhost:8080/api/profile Authorization: Bearer [JWT_TOKEN] или curl -H "Authorization: Bearer $JWT" localhost:8080/api/profile
1_05_jwt_test: восстанавливаем тесты
Как вариант можно воспользоваться jwt() подменой. Но я делаю все честно, с получением токена:
-
В
AbstractControllerTestдобавляем вспомогательные методыgetJWTс Basic авторизацией -
Добавляем тесты на получения JWT:
TokenControllerTest -
Для API добавляем вспомогательный
AbstractControllerTest#performJwtс аторизационным Bearer заголовком. Чтобы токены не перезапрашивать в каждом тесте, кэшируем их вAbstractControllerTest#userJwtMap(синхронизация не нужна — не проблема, если даже они перевычислятся несколько раз) -
Рефакторим тесты API с использованием этих методов. Где нет авторизации, остаются старые методы
perform
Проверяемся: gradle test
Код открытый, можно модифицировать. Ссылка на источник не обязательна, но, если сделаете, буду признателен:) Если код нравится — проголосуйте «за», если нет — напишите замечание в комментарии. Если вы ждали объяснений работы JWT — не надо минусовать статью — вы плохо прочли аннотацию к ней.
Приятного кодинга!
ссылка на оригинал статьи https://habr.com/ru/post/684270/
Добавить комментарий