Сегодня мы займемся практическим внедрением JWT в наше приложение. Мы добавим генерацию и валидацию токенов, настроим фильтры безопасности и обеспечим защиту REST API. Для этого мы создадим небольшое Spring Boot приложение, где авторизация будет происходить через JWT.
JWT – это не просто модное слово. Это удобный, лёгкий и широко используемый механизм аутентификации в современных веб-приложениях. Например, если вы разработчик и идёте на собеседование, будьте уверены: вам зададут вопрос "расскажите про JWT". А если нет (повезло!), то в реальной жизни вы с ним столкнетесь на любом проекте, связанном с аутентификацией.
План нашего приложения
Наше приложение будет состоять из двух основных функциональностей:
- Аутентификация — пользователь отправляет свои учётные данные (логин и пароль), а сервер возвращает JWT.
- Доступ к защищённым ресурсам — пользователь передаёт JWT в заголовке запросов, и если токен валиден, сервер предоставляет доступ к ресурсу.
Шаг 1. Подготовка: конфигурация проекта
Сначала создадим Spring Boot проект. Мы будем использовать:
- Maven для управления зависимостями.
- Spring Boot версии 2.7+.
- Зависимости:
spring-boot-starter-security,spring-boot-starter-web,jjwt(Java JWT библиотека).
Конфигурация Maven Создайте проект с помощью Spring Initializr (https://start.spring.io/). Добавьте следующие зависимости в pom.xml:
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Java 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>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>
Шаг 2. Настройка SecurityConfig
Нам нужно настроить нашу безопасность и убрать стандартную форму логина Spring Security. Заменим её на авторизацию через JWT.
Создайте класс SecurityConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // Доступ к этим маршрутам без авторизации
.anyRequest().authenticated() // Все остальные маршруты требуют авторизации
.and()
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Мы отключили CSRF (только для простоты учебного примера), настроили доступ к эндпоинтам /api/auth/** без авторизации и потребовали авторизацию для всех остальных запросов.
Шаг 3. Создание сущности пользователя
Нам нужно создать сущность пользователя для имитации процесса аутентификации. Для простоты мы будем хранить пользователей в памяти.
public class User {
private String username;
private String password;
// Конструкторы, геттеры и сеттеры
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
Шаг 4. Создание сервиса для генерации JWT
Теперь давайте реализуем сервис, который будет генерировать JWT.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class JwtService {
private final String SECRET_KEY = "your-256-bit-secret"; // Никогда не храним секреты в коде в реальных проектах
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) // 30 минут
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
Шаг 5. Контроллер для аутентификации
Создадим контроллер, который будет обрабатывать запросы для аутентификации.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private JwtService jwtService;
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/login")
public String login(@RequestBody User loginRequest) {
// Пример: статический пользователь для тестов
User user = new User("admin", passwordEncoder.encode("password"));
if (user.getUsername().equals(loginRequest.getUsername()) &&
passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
return jwtService.generateToken(user.getUsername());
}
throw new RuntimeException("Неверные учетные данные");
}
}
Этот эндпоинт принимает username и password, сравнивает их с данными пользователя (в данном случае закодированными в памяти), и если всё валидно, возвращает JWT.
Шаг 6. Валидация JWT
Теперь мы добавим фильтр, который будет обрабатывать каждый запрос, проверяя переданный токен.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final String SECRET_KEY = "your-256-bit-secret";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // Убираем "Bearer "
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
// Устанавливаем аутентификацию
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
System.out.println("Недействительный токен.");
}
}
filterChain.doFilter(request, response);
}
}
Не забудьте зарегистрировать этот фильтр в конфигурации.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
Шаг 7. Тестирование
Теперь мы можем протестировать приложение:
- Отправьте POST запрос на
/api/auth/loginс JSON:{ "username": "admin", "password": "password" }Вы получите JWT в ответе.
- Используйте этот JWT в заголовке
Authorizationдля любого защищённого маршрута:Authorization: Bearer <ваш-токен>
Вот и всё! Мы защитили наш REST API с помощью JWT. В реальной жизни вам потребуется добавить множество улучшений: хранение пользователей в базе данных, обработка Refresh токенов и многое другое. Но теперь вы знаете основные принципы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ