Ласкаво просимо до однієї з найцікавіших тем, пов'язаних із безпекою в сучасних веб-додатках! Сьогодні зануримося в світ 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" — твердження, які токен повідомляє системі.
Приклад payload:
{
"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 — це потужний інструмент, але він не захищає від усіх проблем безпеки. Використовуйте його розумно, і все буде добре!
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ