JavaRush /Курси /Модуль 5. Spring /Лекція 103: Основи JSON Web Token (JWT) та його структура...

Лекція 103: Основи JSON Web Token (JWT) та його структура

Модуль 5. Spring
Рівень 18 , Лекція 2
Відкрита

Ласкаво просимо до однієї з найцікавіших тем, пов'язаних із безпекою в сучасних веб-додатках! Сьогодні зануримося в світ JSON Web Token (JWT), який став де-факто стандартом для аутентифікації та авторизації, особливо в мікросервісних архітектурах. Якщо ви почули абревіатуру JWT уперше, не переймайтеся — наприкінці лекції в нас усе буде "закодовано", причому буквально.


Що таке JSON Web Token (JWT)?

JSON Web Token (вимовляється "Джі-Ві-Ті", але для тих, хто любить гумор, можна казати "ДжавоТі") — це компактний, самодостатній спосіб передачі інформації між двома сторонами у вигляді JSON-об'єкта. JWT використовується для аутентифікації і передачі даних таким чином, щоб їх можна було перевірити, не звертаючись до сервера.

Давайте розберемося, у чому його фішка:

  1. Компактність: JWT — це рядок, який легко передавати через URL, HTTP-заголовки або в тілі запиту.
  2. Самодостатність: увесь "контекст" (наприклад, дані про користувача) міститься всередині токена.
  3. Безпека: токени підписані, тому ніхто не зможе підробити їхні дані. Якщо використовується алгоритм 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 у реальному житті?

Аутентифікація:

  1. Користувач надсилає свої дані для входу (логін і пароль) на сервер.
  2. Сервер перевіряє дані, якщо все гаразд — генерує JWT.
  3. JWT повертається користувачу, який тепер зберігає його (найчастіше в localStorage або cookie).
  4. При кожному запиті до сервера клієнт відсилає 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

  1. Секрети в коді. Ніколи не зберігайте секретний ключ прямо в застосунку. Використовуйте менеджери секретів або змінні оточення.
  2. Токени, що не оновлюються. Якщо токен спливає, а механізм Refresh токенів не налаштований, користувачі змушені будуть повторно аутентифікуватися.
  3. Переповнення корисного навантаження. Пам'ятайте, що всі дані в токені передаються клієнту. Зайва інформація може сповільнити систему і створити ризики безпеки.

Зверніть увагу, що JWT — це потужний інструмент, але він не захищає від усіх проблем безпеки. Використовуйте його розумно, і все буде добре!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ