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

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

Модуль 5. Spring
11 уровень , 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) — утверждения, которые токен сообщает системе.

Пример полезной нагрузки:


{
  "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 — это мощный инструмент, но он не защищает вас от всех проблем безопасности. Используйте его разумно, и вы будете на высоте!

Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ