Добро пожаловать в одну из самых интересных тем, связанных с безопасностью в современных веб-приложениях! Сегодня мы погружаемся в мир JSON Web Token (JWT), который стал стандартом де-факто для аутентификации и авторизации, особенно в микросервисных архитектурах. Если вы слышите аббревиатуру JWT впервые, не переживайте, в конце лекции у нас с вами всё будет "закодировано", причём буквально.
Что такое JSON Web Token (JWT)?
JSON Web Token (произносится "Джи-Ви-Ти", но для тех, кто любит юмор, можно говорить "ДжавоТи") — это компактный, самодостаточный способ передачи информации между двумя сторонами в виде JSON-объекта. JWT используется для аутентификации и передачи информации таким образом, чтобы её можно было проверить, не общаясь с сервером.
Давайте разберёмся, в чём его магия:
- Компактность: JWT представляет собой строку, которая легко передаётся через URL, заголовки HTTP или в теле запроса.
- Самодостаточность: весь "контекст" (например, данные о пользователе) содержится внутри токена.
- Безопасность: токены подписаны, поэтому никто не сможет подделать их данные. Если используется алгоритм HMAC или RSA, данные также шифруются.
Структура JWT
JWT состоит из трёх частей, разделённых точками (.):
header.payload.signature
1.Header (заголовок):** содержит метаданные о JWT, например тип токена (обычно "JWT") и алгоритм подписи (например, HMAC SHA256 или RSA).
Пример:
{
"alg": "HS256",
"typ": "JWT"
}
Эта часть токена кодируется в Base64Url.
2. *Payload (полезная нагрузка):* это основная часть токена, содержащая данные. Здесь могут быть произвольные "клеймы" (claims) — утверждения, которые токен сообщает системе.
Пример полезной нагрузки:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"exp": 1633024800
}
Обратите внимание на следующее:
sub— идентификатор пользователя.exp(expiration) — срок действия токена (число в секундах с эпохи Unix, после которого токен больше не действителен).
3. *Signature (подпись):* создаётся для обеспечения целостности токена. Она рассчитывается с использованием алгоритма из заголовка, закодированных header и payload, а также секретного ключа.
Формула подписи выглядит так:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
Пример "собранного" JWT:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Как работает JWT в реальной жизни?
Аутентификация:
- Пользователь отправляет свои данные для входа (логин и пароль) на сервер.
- Сервер проверяет данные, если всё в порядке — генерирует JWT.
- JWT возвращается пользователю, который теперь хранит его (чаще всего, это localStorage или cookie).
- При каждом запросе к серверу клиент отправляет JWT в заголовке
Authorization.
Пример заголовка:
Authorization: Bearer <your-jwt-token>
Авторизация:
с помощью информации, содержащейся в payload, сервер может определить роли пользователя (например, admin, user) и предоставить доступ к защищённым ресурсам.
Пример использования JWT в Spring Boot
Давайте сразу нырнём в практику. Мы создадим Spring Boot приложение, которое генерирует и валидирует JWT.
Зависимость в pom.xml
Добавим необходимые библиотеки для работы с JWT:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
Генерация JWT
Создадим сервис для генерации токенов:
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
public class JwtUtil {
private String secret = "mySecretKey"; // Никогда не храним секрет в коде в реальных проектах!
// Генерация токена
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
// Создание токена
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 часов
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
}
}
Расшифровка и проверка JWT
Добавим метод для проверки токена:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
public class JwtUtil {
private String secret = "mySecretKey";
// Проверка токена
public Claims extractClaims(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public String extractUsername(String token) {
return extractClaims(token).getSubject();
}
}
}
Теперь сервер может декодировать токен и извлечь из него полезную информацию, например, имя пользователя.
Защита REST API
В Spring Security мы можем добавить фильтр, который проверяет JWT в каждом запросе. Пример фильтра:
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
public class JwtFilter extends OncePerRequestFilter {
private JwtUtil jwtUtil = new JwtUtil();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws java.io.IOException, jakarta.servlet.ServletException {
String authorizationHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
token = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(token);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// В данном случае мы бы проверили пользователя (например, через базу данных).
// Код проверки исключён ради упрощения.
}
chain.doFilter(request, response);
}
}
Этот фильтр подключается в вашей конфигурации Spring Security.
Типичные ошибки при работе с JWT
- Секреты в коде. Никогда не храните секретный ключ прямо в приложении. Используйте менеджеры секретов или переменные окружения.
- Необновляемые токены. Если токен истекает, а механизм Refresh токенов не настроен, пользователи будут вынуждены повторно аутентифицироваться.
- Переполнение полезной нагрузки. Помните, что все данные в токене передаются клиенту. Лишняя информация может замедлить систему и создать риски безопасности.
Обратите внимание, что JWT — это мощный инструмент, но он не защищает вас от всех проблем безопасности. Используйте его разумно, и вы будете на высоте!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ