# Извлечение информации о пользователе из JWT токена ## Обзор Данная документация описывает, как извлечь информацию о пользователе из JWT токена в микросервисе на Spring Boot. ## Структура JWT токена JWT токен содержит следующую информацию: - **Subject (sub)**: Email пользователя - **Custom Claims**: - `uid`: ID пользователя (Long) - `roles`: Роли пользователя (String, разделённые запятыми, например: "ROLE_USER,ROLE_ADMIN") - **Стандартные поля**: `iat` (issued at), `exp` (expiration) ## Зависимости Убедитесь, что в `pom.xml` добавлена зависимость: ```xml io.jsonwebtoken jjwt-api 0.12.3 io.jsonwebtoken jjwt-impl 0.12.3 runtime io.jsonwebtoken jjwt-jackson 0.12.3 runtime ``` ## Конфигурация В `application.properties` или `application.yml`: ```properties security.jwt.secret-base64= security.jwt.access-ttl-seconds=3600 ``` **Важно**: Используйте тот же `secret-base64`, что и в сервисе, выдающем токены. ## Создание JwtService ```java package com.example.service; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.security.Key; @Service public class JwtService { private final Key signingKey; public JwtService( @Value("${security.jwt.secret-base64}") String base64Secret) { this.signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(base64Secret)); } public Claims parseAndValidate(String token) { return Jwts.parserBuilder() .setSigningKey(signingKey) .build() .parseClaimsJws(token) .getBody(); } } ``` ## Извлечение информации о пользователе ### Вариант 1: Из заголовка Authorization ```java import io.jsonwebtoken.Claims; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") public class UserController { private final JwtService jwtService; public UserController(JwtService jwtService) { this.jwtService = jwtService; } @GetMapping("/user-info") public ResponseEntity getUserInfo( @RequestHeader(HttpHeaders.AUTHORIZATION) String authHeader) { // Извлекаем токен из заголовка "Bearer " if (authHeader == null || !authHeader.startsWith("Bearer ")) { return ResponseEntity.status(401).build(); } String token = authHeader.substring(7); try { Claims claims = jwtService.parseAndValidate(token); // Извлекаем информацию String email = claims.getSubject(); Long userId = claims.get("uid", Long.class); String rolesString = claims.get("roles", String.class); // Парсим роли List roles = rolesString == null || rolesString.isBlank() ? List.of() : Arrays.stream(rolesString.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .collect(Collectors.toList()); UserInfo userInfo = new UserInfo(userId, email, roles); return ResponseEntity.ok(userInfo); } catch (Exception e) { // Токен невалиден или истёк return ResponseEntity.status(401).build(); } } } ``` ### Вариант 2: Использование Spring Security (рекомендуется) Если в вашем микросервисе настроен Spring Security с JWT фильтром, используйте `Principal`: ```java import java.security.Principal; import org.springframework.security.access.prepost.PreAuthorize; @RestController @RequestMapping("/api") public class UserController { private final JwtService jwtService; public UserController(JwtService jwtService) { this.jwtService = jwtService; } @GetMapping("/me") @PreAuthorize("isAuthenticated()") public ResponseEntity getCurrentUser(Principal principal) { // Principal.getName() возвращает subject (email) из JWT String email = principal.getName(); // Если нужны дополнительные данные (uid, roles), // можно извлечь их из SecurityContext или извлечь токен из запроса Authentication auth = SecurityContextHolder.getContext().getAuthentication(); // Или получить токен из запроса и распарсить // (см. Вариант 1 для полного извлечения всех claims) return ResponseEntity.ok(new UserInfo(null, email, List.of())); } } ``` ### Вариант 3: Полное извлечение через HttpServletRequest ```java import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api") public class UserController { private final JwtService jwtService; @GetMapping("/profile") public ResponseEntity getProfile(HttpServletRequest request) { String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION); if (authHeader == null || !authHeader.startsWith("Bearer ")) { return ResponseEntity.status(401).build(); } String token = authHeader.substring(7); Claims claims = jwtService.parseAndValidate(token); String email = claims.getSubject(); Long userId = claims.get("uid", Long.class); String rolesString = claims.get("roles", String.class); List roles = parseRoles(rolesString); return ResponseEntity.ok(new UserInfo(userId, email, roles)); } private List parseRoles(String rolesString) { if (rolesString == null || rolesString.isBlank()) { return List.of(); } return Arrays.stream(rolesString.split(",")) .map(String::trim) .filter(s -> !s.isEmpty()) .collect(Collectors.toList()); } } ``` ## DTO для пользователя ```java public record UserInfo( Long userId, String email, List roles ) {} ``` ## Обработка ошибок ```java @ControllerAdvice public class JwtExceptionHandler { @ExceptionHandler(JwtException.class) public ResponseEntity handleJwtException(JwtException e) { return ResponseEntity.status(401) .body(new ErrorResponse("Invalid or expired token", 401)); } } ``` ## Пример использования в сервисном слое ```java @Service public class BusinessService { private final JwtService jwtService; public BusinessService(JwtService jwtService) { this.jwtService = jwtService; } public void processRequest(String token) { Claims claims = jwtService.parseAndValidate(token); Long userId = claims.get("uid", Long.class); String email = claims.getSubject(); // Используйте userId и email для бизнес-логики // ... } } ``` ## Важные замечания 1. **Валидация токена**: Метод `parseAndValidate` автоматически проверяет: - Подпись токена - Срок действия (expiration) - Формат токена 2. **Безопасность**: Никогда не логируйте полный JWT токен или секретный ключ. 3. **Секретный ключ**: Должен совпадать с ключом в сервисе, выдающем токены. 4. **Обработка исключений**: `JwtException` и его подклассы (`ExpiredJwtException`, `MalformedJwtException`, и т.д.) должны обрабатываться корректно. ## Примеры исключений - `ExpiredJwtException`: Токен истёк - `MalformedJwtException`: Неверный формат токена - `SignatureException`: Неверная подпись - `UnsupportedJwtException`: Неподдерживаемый тип токена ## Тестирование ```java @SpringBootTest class JwtServiceTest { @Autowired private JwtService jwtService; @Test void testParseToken() { String token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; Claims claims = jwtService.parseAndValidate(token); assertEquals("user@example.com", claims.getSubject()); assertEquals(123L, claims.get("uid", Long.class)); assertEquals("ROLE_USER", claims.get("roles", String.class)); } } ```