TL;DR: Spring Security + JWT: за 10 минут добавляем безопасную аутентификацию и авторизацию в проект на Spring Boot.
Коротко: В статье показано, как быстро добавить JWT‑аутентификацию в проект на Spring Boot с использованием Spring Security. Вы увидите пошаговый пример конфигурации, генерации токенов и защиты эндпоинтов за 10 минут. Это идеальный старт для тех, кто хочет быстро интегрировать безопасную авторизацию в своё приложение.
Введение: Что такое Spring Security + JWT и зачем использовать их в микросервисах
Когда я впервые задумался о создании микросервисной архитектуры, сразу понял, что безопасность – это не просто «красивый штрих», а фундамент, который должен держать всё целиком. В этом контексте Spring Security и Spring Boot стали для меня как два мощных инструмента, готовых к работе: Spring Security обеспечивает гибкую и расширяемую систему аутентификации и авторизации, а Spring Boot позволяет быстро стартовать и сосредоточиться на бизнес‑логике, а не на настройке окружения.
Но как же сделать так, чтобы каждый сервис в кластере «знал» о пользователе, не пересылая пароли по сети и не запрашивая базу каждый раз? Здесь вступает в игру JWT – JSON Web Token. Внутри Spring Security JWT можно использовать как «пакет» сессии: после успешной аутентификации в одном сервисе клиент получает токен, который содержит все необходимые claims (имя, роли, срок действия). Далее этот токен просто ставится в заголовок Authorization и передаётся любому другому микросервису. Spring Security, настроенный на проверку подписи и сроков жизни токена, мгновенно распознаёт пользователя и применяет нужные правила доступа. Это избавляет от постоянных запросов к центральному хранилищу и делает микросервисы действительно независимыми, но при этом защищёнными.
В статье «spring‑boot‑что‑это» уже упоминалось, как быстро можно поднять базовую конфигурацию Spring Boot. Добавить к ней JWT‑проверку – всего несколько строк кода, и вы получаете надёжную, масштабируемую систему, где безопасность не мешает, а ускоряет разработку.
Подготовка проекта: Создание Spring Boot приложения с помощью стартеров и автоконфигурации
Я решил начать с чистого листа и воспользоваться официальным генератором проектов – Spring Initializr. Открыв https://start.spring.io/, я выбрал Java 21, Maven как систему сборки, и указал группу com.example и артефакт jwt-demo. В разделе «Dependencies» добавил два ключевых стартеров: Spring Web и Spring Security. Благодаря автоинжекции и автоконфигурации эти модули сразу попали в pom.xml, где появилось <dependency>org.springframework.boot:spring-boot-starter-web</dependency> и <dependency>org.springframework.boot:spring-boot-starter-security</dependency>. Это избавляет от ручного прописывания spring-boot-starter и всех его транзитивных зависимостей, таких как spring-webmvc, spring-security-web, spring-security-config и др.
После скачивания архива я распаковал его и открыл в IntelliJ IDEA. Если бы я предпочёл Gradle, то просто выбрал его в Initializr и заменил pom.xml на build.gradle.kts, где аналогично подключаются implementation("org.springframework.boot:spring-boot-starter-web") и implementation("org.springframework.boot:spring-boot-starter-security"). В обоих случаях автоконфигурация Spring Boot автоматически создаёт DispatcherServlet для веб‑части и базовую конфигурацию безопасности, которую мы будем тонко настроить позже. Это уже даёт нам полностью готовую основу, над которой можно быстро приступить к реализации JWT‑аутентификации.
Конфигурация Maven/Gradle: Добавление зависимостей JWT и Spring Security
Когда я впервые разрабатывал REST‑API, сразу понял, что без JWT сложно организовать надёжную аутентификацию. В этом разделе покажу, как быстро добавить нужные библиотеки в ваш проект, будь то Maven или Gradle.
В pom.xml для Maven просто вставьте зависимость от Spring Security и одну от jjwt, которая умеет генерировать и парсить токены.
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- для JSON‑парсинга -->
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
</dependencies>
Если же вы пользуетесь Gradle, то в build.gradle.kts добавьте:
dependencies {
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("io.jsonwebtoken:jjwt-api:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5") // Jackson‑плагин
}
После этого ваш проект будет готов к работе с JWT, а в следующих разделах мы разберём, как настроить сам JwtTokenProvider и интегр
Создание модели пользователя: Java Spring entity и репозиторий
Когда я начал писать модель пользователя, сразу подумал о том, как сделать её максимально «Spring‑friendly». В итоге я создал класс User, помеченный аннотацией @Entity, чтобы Hibernate мог его распознать и превратить в таблицу. Внутри я добавил поля id, username, password и role. Важно было объявить id как @GeneratedValue, чтобы база сама генерировала уникальный ключ. В качестве примера:
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role;
}
Затем я создал репозиторий, наследующий от JpaRepository<User, Long>. Это дало мне сразу доступ к методам save, findById, findAll и т.д., без лишней ручной реализации. Я добавил кастомный метод findByUsername, чтобы быстро находить пользователя по логину:
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
## Генерация JWT: Реализация сервиса генерации и проверки токенов
Когда я приступил к созданию JwtTokenProvider, сразу понял, что это будет ядро всей аутентификации. Внутри Spring Boot я создал отдельный сервис, помеченный аннотацией @Service, который будет отвечать за генерацию и валидацию токенов. В конструкторе я внедрил пару ключевых компонентов: SecretKey для подписи, Duration для срока жизни токена и JwtEncoder из Spring Security. Всё это позволяет держать логику в одном месте и легко менять параметры без пересборки приложения.
При аутентификации я беру объект Authentication, извлекаю из него имя пользователя и роли, а затем формирую Claims с помощью JwtClaimsSet.builder().setSubject(userDetails.getUsername()).claim("roles", userDetails.getAuthorities()).setIssuedAt(Instant.now()).setExpiration(Instant.now().plus(tokenValidity)).build(). После этого токен подписывается и возвращается клиенту в виде строки. Если в запросе уже присутствует JWT, то JwtTokenProvider просто проверяет подпись, срок действия и наличие нужных ролей, чтобы решить, можно ли продлить сессию. Всё это реализовано на чистом Java Spring, используя возможности Spring Security и Spring Boot, что делает код лаконичным и легко расширяемым.
## Конфигурация Spring Security: Настройка WebSecurityConfigurerAdapter и фильтров
Я сразу погрузился в настройку `SecurityFilterChain`, ведь именно он управляет тем, какие запросы проходят через цепочку фильтров, а какие – нет. В Spring Boot 3.2 автоконфигурация упрощает жизнь: достаточно объявить бина `SecurityFilterChain` в конфигурации и указать, что все запросы к `/api/**` должны проходить через JWT‑аутентификацию. Я написал метод `securityFilterChain(HttpSecurity http)`, где включил CORS, отключил CSRF (мы работаем с токенами), а затем добавил свой `JwtAuthenticationFilter` через `http.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)`. Это гарантирует, что токен будет проверяться до того, как Spring попытается выполнить стандартную аутентификацию.
Для самого фильтра я использовал `OncePerRequestFilter`. Внутри `doFilterInternal` я вытаскиваю заголовок `Authorization`, проверяю, начинается ли он с `Bearer `, и если да, то декодирую токен, создаю `UsernamePasswordAuthenticationToken` и ставлю его в `SecurityContextHolder`. Если токен недействителен – просто пропускаю запрос дальше, чтобы Spring смог вернуть 401. Такой подход полностью соответствует рекомендациям Spring Framework и позволяет быстро перейти от простого REST‑API к защищенному сервису без лишних усилий.
## Аутентификация через REST: Создание эндпоинта /login и возврат JWT
Наконец дошло время написать самую вкусную часть: AuthController, который выдаёт JWT после проверки логина и пароля. Я создаю класс с аннотацией `@RestController` и маппингом `/login`. Внутри я внедряю `AuthenticationManager` из Spring Security и наш собственный `JwtTokenProvider`. В методе `authenticate` принимаем DTO с полями `username` и `password`, а затем вызываем `authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user, pass))`. Если проверка проходит, генерируем токен через `jwtTokenProvider.createToken(authentication.getName(), authentication.getAuthorities())` и возвращаем его клиенту в теле ответа.
В микросервисной архитектуре этот эндпоинт становится точкой входа для всех сервисов, которые нуждаются в аутентификации. Я добавляю простую обработку ошибок: если `BadCredentialsException` выбрасывается, возвращаем статус 401 с понятным сообщением. Это делает сервис надёжным и понятным для фронтенда. В итоге, всего за пару строк кода у меня готов полностью рабочий REST‑эндпоинт, выдающий JWT, который можно использовать в дальнейшем для защиты остальных микросервисов.
## Защита эндпоинтов: Применение аннотаций @PreAuthorize и ролей
Когда я подключал Spring Security к своему проекту на Spring Boot, сразу захотелось сделать так, чтобы только администраторы могли работать с разделом `/admin/**`. Самый простой и «чистый» способ — использовать аннотацию `@PreAuthorize` в контроллере. В файле конфигурации я включил поддержку аннотаций, добавив `@EnableGlobalMethodSecurity(prePostEnabled = true)`. Теперь в методах, которые обслуживают `/admin/**`, можно писать:
```java
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/dashboard")
public String adminDashboard() {
return "admin/dashboard";
}
Такой подход позволяет держать бизнес‑логику и безопасность рядом, а не в одном месте. Важно помнить, что роль в JWT должна быть записана как ROLE_ADMIN, иначе Spring не распознает её. Если в токене будет ADMIN, Spring добавит префикс ROLE_ автоматически. Благодаря @PreAuthorize любой запрос к /admin/** от пользователя без роли ADMIN будет автоматически отклонён с кодом 403. Это делает защиту надёжной и легко масштабируемой, ведь менять правила можно просто обновив аннотации, без изменения конфигурации безопасности.
Тестирование и отладка: Проверка работы JWT в Postman и unit‑тестах
После того как мы настроили фильтр JWT и интегрировали его в Spring Security, пришло время убедиться, что всё работает так, как задумано. Я открыл Postman, сконфигурировал запрос к /auth/login и получил токен. Далее я копирую его в заголовок Authorization: Bearer <token> и отправляю запрос к защищенному эндпоинту /api/secure. Если получаем 200 OK и ожидаемый JSON, значит фильтр корректно извлекает токен и передаёт его дальше. Если же сервер отвечает 401 Unauthorized, я сразу проверяю, не пропал ли пробел после Bearer и правильно ли выставлен Content-Type. В Postman можно быстро менять токен, проверять его истечение и убедиться, что при старом токене сервер действительно отклоняет запрос.
Для автоматизации я написал unit‑тесты с использованием @SpringBootTest и MockMvc. В JwtAuthenticationFilterTest я генерирую тестовый токен через JwtTokenProvider, затем отправляю запрос к защищенному контроллеру, передавая токен в заголовке. С помощью andExpect(status().isOk()) проверяю, что эндпоинт доступен. Также я добавил тест, где токен не передаётся – ожидаю 401. Всё это собирается и запускается через Maven (mvn test), а отчёт выводится в консоль. Такой подход позволяет быстро обнаруживать регрессии в логике аутентификации и гарантировать, что
Развертывание микросервиса: Docker‑образ и настройка переменных окружения
Наконец дошёл момент, когда всё, что мы писали, можно запаковать в контейнер и развернуть в реальной среде. Открываю новый файл Dockerfile и пишу:
Коротко, но ёмко: берём легковесный образ OpenJDK, копируем наш JAR, открываем порт и указываем команду запуска. Благодаря автоконфигурации Spring Boot всё, что нужно, уже настроено – только переменные окружения.
Собираю образ командой docker build -t auth-service . и запускаю:
Здесь -e передаёт секреты в контейнер, а SPRING_PROFILES_ACTIVE переключает профиль на продакшн. Таким образом микросервис получает все нужные данные без лишних файлов конфигурации, а Docker гарантирует изоляцию и воспроизводимость. Это именно то, что делает микросервисы гибкими и масштабируемыми.
Итог
FAQ
Какой алгоритм подписи JWT лучше использовать? Рекомендуется RS256 (асимметричный) для production — приватный ключ для подписи, публичный для проверки. HS256 (симметричный) проще, но требует общего секрета.
Как настроить refresh-токены в Spring Security? Создайте отдельный эндпоинт /auth/refresh, который принимает refresh-токен, проверяет его подпись и срок действия, и возвращает новый access-токен. Храните refresh-токены в безопасном cookie или в БД.
Как защитить разные эндпоинты разными ролями? Используйте @PreAuthorize с выражениями hasRole, hasAuthority или hasAnyRole. Включите @EnableGlobalMethodSecurity(prePostEnabled=true) и настройте роли в claims JWT.