Объясните принцип работы JWT-аутентификации.java-49

JWT (JSON Web Token) — это компактный и самодостаточный способ передачи информации между сторонами в виде JSON-объекта. JWT часто используется для аутентификации и авторизации в веб-приложениях. Давайте разберем, как работает JWT-аутентификация.

1. Структура JWT

JWT состоит из трех частей, разделенных точками (.):

1.1. Заголовок

Заголовок содержит информацию о типе токена и алгоритме шифрования.

{
  "alg": "HS256",
  "typ": "JWT"
}

1.2. Полезная нагрузка

Полезная нагрузка содержит claims (утверждения), которые могут быть зарегистрированными, публичными или приватными.

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

1.3. Подпись

Подпись используется для проверки того, что токен не был изменен. Она создается путем кодирования заголовка и полезной нагрузки с использованием секретного ключа.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

2. Процесс JWT-аутентификации

2.1. Аутентификация пользователя

Пользователь отправляет свои учетные данные (например, имя пользователя и пароль) на сервер для аутентификации.

@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) {
    User user = userService.authenticate(loginRequest.getUsername(), loginRequest.getPassword());
    if (user != null) {
        String token = jwtTokenUtil.generateToken(user);
        return ResponseEntity.ok(token);
    } else {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
    }
}

2.2. Генерация JWT

Если аутентификация успешна, сервер генерирует JWT и возвращает его клиенту.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

public class JwtTokenUtil {

    private String secret = "mySecretKey";

    public String generateToken(User user) {
        return Jwts.builder()
                .setSubject(user.getUsername())
                .claim("roles", user.getRoles())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1 час
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }
}

2.3. Передача JWT

Клиент сохраняет JWT (обычно в localStorage или cookies) и включает его в заголовок Authorization при каждом запросе к защищенным ресурсам.

Authorization: Bearer <JWT>

2.4. Верификация JWT

Сервер проверяет JWT на каждом запросе, чтобы убедиться, что токен действителен и не истек.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

public class JwtTokenUtil {

    private String secret = "mySecretKey";

    public Claims validateToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}

2.5. Защита ресурсов

Сервер проверяет JWT и разрешает доступ к защищенным ресурсам, если токен действителен.

@GetMapping("/protected")
public ResponseEntity<String> protectedResource(@RequestHeader("Authorization") String authHeader) {
    String token = authHeader.substring(7); // Убираем "Bearer "
    Claims claims = jwtTokenUtil.validateToken(token);
    if (claims != null) {
        return ResponseEntity.ok("Access granted to protected resource");
    } else {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token");
    }
}

3. Преимущества JWT

  • Без состояния (Stateless): JWT не требует хранения состояния на сервере, что упрощает масштабирование.
  • Компактность (Compact): JWT компактен и может быть легко передан через URL, POST-параметры или заголовки HTTP.
  • Безопасность (Security): JWT может быть подписан и зашифрован, что обеспечивает безопасность передачи данных.

4. Пример полной реализации JWT-аутентификации

Рассмотрим пример полной реализации JWT-аутентификации в Spring Boot.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Service;

import java.util.Date;

@Service
public class JwtTokenUtil {

    private String secret = "mySecretKey";

    public String generateToken(User user) {
        return Jwts.builder()
                .setSubject(user.getUsername())
                .claim("roles", user.getRoles())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1 час
                .signWith(SignatureAlgorithm.HS256, secret)
                .compact();
    }

    public Claims validateToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final UserService userService;
    private final JwtTokenUtil jwtTokenUtil;

    public AuthController(UserService userService, JwtTokenUtil jwtTokenUtil) {
        this.userService = userService;
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @PostMapping("/login")
    public ResponseEntity<String> login(@RequestBody LoginRequest loginRequest) {
        User user = userService.authenticate(loginRequest.getUsername(), loginRequest.getPassword());
        if (user != null) {
            String token = jwtTokenUtil.generateToken(user);
            return ResponseEntity.ok(token);
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
        }
    }

    @GetMapping("/protected")
    public ResponseEntity<String> protectedResource(@RequestHeader("Authorization") String authHeader) {
        String token = authHeader.substring(7); // Убираем "Bearer "
        Claims claims = jwtTokenUtil.validateToken(token);
        if (claims != null) {
            return ResponseEntity.ok("Access granted to protected resource");
        } else {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid token");
        }
    }
}

Резюмируем

JWT-аутентификация — это мощный и гибкий способ управления аутентификацией и авторизацией в веб-приложениях. JWT состоит из трех частей: заголовка, полезной нагрузки и подписи. Процесс аутентификации включает генерацию токена, его передачу и верификацию на сервере. JWT обеспечивает безопасность, компактность и отсутствие необходимости хранения состояния на сервере, что делает его популярным выбором для современных веб-приложений.