# Извлечение информации о пользователе из 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));
}
}
```