Перейти к основному содержимому

JWT и OAuth2 Resource Server в Spring Boot

Разработчику Архитектору

JWT и OAuth2 Resource Server в Spring Boot

После HTTP Basic клиенту неудобно слать логин и пароль в каждом запросе. Схема JWT: один раз POST /auth/login → сервер отдаёт токен → дальше только Authorization: Bearer eyJhbG....

Stateless значит: сервер не хранит сессию в памяти; вся информация о пользователе и сроке жизни — внутри подписанного токена. Микросервисы масштабируются проще: любой инстанс проверяет подпись тем же секретом или публичным ключом.

Здесь — учебный монолит: свой эндпоинт выдачи токена + OAuth2 Resource Server для проверки. В проде токены обычно выдаёт Keycloak, Auth0 или корпоративный IdP — см. Spring Framework § Security и чеклист безопасности в продакшене.

Перед стартом: Spring Boot — первая программа, Spring Security — старт.


Словарь

ТерминОбъяснение
JWT (JSON Web Token)Строка из трёх частей в Base64: заголовок, полезная нагрузка (claims), подпись
ClaimПоле внутри JWT: sub (кто), exp (когда истекает), scope, roles
Bearer"Предъявитель токена" — тип авторизации в заголовке Authorization: Bearer ...
Resource ServerВаш API, который принимает и проверяет токены (в отличие от сервера, который их выдаёт)
JwtEncoder / JwtDecoderПодписать токен при логине / проверить подпись и срок при запросе
HS256Симметричный алгоритм: один секрет и для подписи, и для проверки (учебный вариант)
RS256Асимметричный: приватный ключ подписывает, публичный проверяет (типично в проде + JWKS)
Как проходить статью

Сначала запустите приложение и получите токен через POST /auth/login, затем вызовите /api/me с заголовком Authorization: Bearer .... Только после этого разбирайте JwtEncoder и JwtDecoder.


Структура JWT (что внутри строки)

Токен выглядит как xxxxx.yyyyy.zzzzz (три блока через точку):

  1. Header — алгоритм (HS256) и тип JWT.
  2. Payload — JSON с claims (sub, exp, …); не шифруется, только подписывается — не кладите в JWT пароли и персональные данные без необходимости.
  3. Signature — HMAC от header+payload секретом; подмена payload ломает подпись → 401.

Проверка на сервере — декодировать payload, убедиться что exp в будущем, пересчитать подпись тем же секретом.


Basic и JWT (когда что)

HTTP BasicJWT (Bearer)
Логин/пароль в каждом запросе (Base64 в заголовке)Токен после /auth/login
Проще для curl и внутренних скриптовУдобно мобильным приложениям и SPA
Сессия на сервере опциональнаОбычно без серверной сессии (stateless)
Пароль "светится" чащеПароль один раз на /login; дальше только токен с ограниченным сроком

Что получится

# 1) Получить токен
curl -s -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"demo","password":"secret"}'

# 2) Вызвать API с токеном (подставьте access_token из ответа)
curl -s http://localhost:8080/api/me \
-H "Authorization: Bearer eyJhbG..."

Разбор:

  • Здесь показан воспроизводимый сценарий команд curl`, `-H`, `-d для проверки темы раздела на реальном запуске.
  • Команды идут как цепочка шагов: подготовка окружения, вызов инструмента и проверка ответа/результата.
  • Такой блок полезен тем, что его можно выполнить без IDE и быстро подтвердить, что пример живой.
  • Если результат отличается от ожидаемого, причина почти всегда в окружении или порядке выполнения строк.
  • Практически этот же набор команд переносится в CI как smoke-проверка документационного примера.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

Разбор:

  • XML-блок через теги dependency`, `groupId`, `artifactId описывает конфигурацию декларативно, без ручной Java-инициализации.
  • Смысл фрагмента в том, что сборщик/контейнер читает структуру файла и сам подтягивает нужные зависимости.
  • Ошибка в одном теге или координате сразу ломает весь сценарий, поэтому это критическая часть примера.
  • Такой подход отделяет код приложения от инфраструктурных настроек и упрощает сопровождение.
app:
jwt:
# минимум 32 байта для HS256
secret: "change-me-change-me-change-me-32b!"

Разбор:

  • Ключи app`, `jwt`, `secret задают runtime-поведение через конфигурацию, а не через изменения в коде.
  • Отступы формируют иерархию параметров, поэтому структура файла влияет на то, какие настройки реально применятся.
  • Фрагмент полезен как точка контроля окружения — порт, логирование, имя сервиса или security-параметры.
  • На практике такие блоки связывают с профилями и переменными окружения для dev/test/prod.

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте ключевой объект — JwtConfig; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @Configuration, @Bean, @Value включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах secretKey()`, `Value()`, `getBytes()`, `SecretKeySpec(): они задают путь от входа к результату.
  • По return видно, какой итог выходит наружу: объект домена, HTTP-ответ или конфигурационный результат.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте ключевой объект — SecurityConfig; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @Configuration, @EnableWebSecurity, @Bean включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах filterChain()`, `csrf()`, `disable()`, `authorizeHttpRequests(): они задают путь от входа к результату.
  • По return видно, какой итог выходит наружу: объект домена, HTTP-ответ или конфигурационный результат.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.

Код ITЗагрузка примера кода…

Разбор:

  • В этом фрагменте ключевой объект — LoginRequest, плюс TokenResponse; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @RestController, @RequestMapping, @PostMapping, @RequestBody включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах LoginRequest()`, `TokenResponse()`, `RequestMapping()`, `AuthController(): они задают путь от входа к результату.
  • Условия if в этом блоке явно отделяют валидный и невалидный сценарий, чтобы ошибка отрабатывала до выполнения основной операции.
  • По return видно, какой итог выходит наружу: объект домена, HTTP-ответ или конфигурационный результат.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.
@RestController
public class MeController {

@GetMapping("/api/me")
public Map<String, Object> me(@AuthenticationPrincipal Jwt jwt) {
return Map.of(
"subject", jwt.getSubject(),
"expiresAt", jwt.getExpiresAt().toString()
);
}
}

Разбор:

  • В этом фрагменте ключевой объект — MeController; через него показан конкретный кусок архитектуры из раздела.
  • Аннотации @RestController, @GetMapping, @AuthenticationPrincipal включают фреймворковое поведение — регистрация, биндинг, security или тестовая инфраструктура.
  • Основной поток строится на вызовах GetMapping()`, `me()`, `of()`, `getSubject(): они задают путь от входа к результату.
  • По return видно, какой итог выходит наружу: объект домена, HTTP-ответ или конфигурационный результат.
  • При расширении этого примера сначала проверяют контракты методов и тесты на граничные случаи — именно там чаще всего возникает регрессия.
POST /auth/login → JwtEncoder → access_token
GET /api/me + Bearer → JwtAuthenticationFilter → JwtDecoder → контроллер

Разбор:

  • Этот блок работает как схема: строка POST /auth/login → JwtEncoder → access_token задаёт каркас потока без деталей реализации.
  • Сначала видна общая последовательность этапов, а затем уже разбираются конкретные классы и методы.
  • Такой формат уменьшает когнитивную нагрузку и помогает быстрее локализовать проблемный шаг.
  • Практически это карта для отладки и чтения следующих кодовых фрагментов.