В этой лекции мы разберём:
- Как настроить Spring Boot для работы с JWT.
- Реализацию генерации JWT токенов при успешной аутентификации.
- Настройку фильтра безопасности для проверки токенов в запросах.
- Управление доступом к защищённым ресурсам с помощью JWT и Spring Security.
Готовы? Заваривайте кофе и поехали!
Настройка Spring Boot для работы с JWT
Для начала создадим базовый проект Spring Boot. Убедитесь, что ваш pom.xml или build.gradle включает необходимые зависимости:
Зависимости
Если вы используете Maven, добавьте это в ваш pom.xml:
<dependencies>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT библиотека -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>
Или, если вы используете Gradle:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
}
Эти зависимости обеспечат все необходимые инструменты для реализации JWT аутентификации.
Генерация токенов JWT
Для начала сгенерируем JWT токены. Создадим класс JwtTokenUtil, который будет отвечать за создание и валидацию токенов.
Реализация JwtTokenUtil
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtTokenUtil {
private static final String SECRET_KEY = "secret"; // Секретный ключ (не используйте такие простые ключи в реальной жизни!)
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 часов
// Генерация токена
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>(); // Здесь можно добавить кастомные данные
return Jwts.builder()
.setClaims(claims)
.setSubject(username) // Устанавливаем имя пользователя как subject токена
.setIssuedAt(new Date()) // Дата генерации токена
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // Время истечения
.signWith(SignatureAlgorithm.HS512, SECRET_KEY) // Используем алгоритм HMAC-SHA512 для подписи
.compact();
}
// Валидация токена (проверяем, не истёк ли токен и совпадает ли пользователь)
public boolean validateToken(String token, String username) {
final String tokenUsername = extractUsername(token);
return (username.equals(tokenUsername) && !isTokenExpired(token));
}
// Извлечение имени пользователя из токена
public String extractUsername(String token) {
return extractAllClaims(token).getSubject();
}
// Проверка истечения токена
public boolean isTokenExpired(String token) {
return extractAllClaims(token).getExpiration().before(new Date());
}
// Извлечение всех claims (полезная нагрузка токена)
private Claims extractAllClaims(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
}
В этом классе мы реализовали основные методы для работы с JWT. Теперь мы можем генерировать токены, валидировать их, а также извлекать данные из payload.
Защита ресурсов с помощью JWT
Теперь перейдём к настройке безопасности. Мы будем использовать Spring Security для фильтрации запросов и проверки токенов.
Настройка фильтра безопасности
Добавим фильтр, который будет перехватывать запросы, извлекать JWT из заголовка запроса и проверять его.
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtTokenUtil jwtTokenUtil;
public JwtRequestFilter(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
// Извлекаем токен из заголовка "Authorization: Bearer ..."
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtTokenUtil.extractUsername(jwt);
}
// Проверяем токен и устанавливаем контекст безопасности
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
if (jwtTokenUtil.validateToken(jwt, username)) {
// Создать аутентификацию на основе токена (упрощённый пример)
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, new ArrayList<>()
);
auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
// Пропускаем запрос дальше
chain.doFilter(request, response);
}
}
Этот фильтр будет выполнять проверку токена для каждого запроса и добавлять информацию о пользователе в контекст безопасности.
Конфигурация Spring Security
Создадим класс конфигурации безопасности, чтобы зарегистрировать наш JwtRequestFilter и настроить доступ к защищённым ресурсам.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.build();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests()
.requestMatchers("/authenticate").permitAll() // Открытый доступ для аутентификации
.anyRequest().authenticated() // Все остальные запросы требуют аутентификации
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Реализация эндпоинта аутентификации
Создадим контроллер, который будет принимать имя пользователя и пароль, а затем возвращать JWT при успешной аутентификации.
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthController {
private final JwtTokenUtil jwtTokenUtil;
public AuthController(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
}
@PostMapping("/authenticate")
public String authenticate(@RequestBody AuthRequest authRequest) {
// Простая проверка имени пользователя (для демонстрации, в жизни используйте UserDetailsService)
if ("user".equals(authRequest.getUsername()) && "password".equals(authRequest.getPassword())) {
return jwtTokenUtil.generateToken(authRequest.getUsername());
} else {
throw new RuntimeException("Неверные учетные данные");
}
}
}
// DTO для запроса аутентификации
class AuthRequest {
private String username;
private String password;
// Getters и Setters
}
Теперь, отправив POST-запрос на /authenticate с верными данными, вы получите JWT, который можно использовать для доступа к защищённым ресурсам.
На этом этапе у нас готово базовое приложение с JWT аутентификацией. Мы создали структуру, которая легко масштабируется для более сложных проектов. В реальной жизни используйте базы данных для хранения пользователей и ролей, а также добавляйте больше уровней проверки безопасности.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ