JWT-токен io.jsonwebtoken.ExpiredJwtException: JWT истек

Вопрос или проблема

У меня есть приложение Spring Boot (с Angular).
Мое приложение работает нормально локально, но когда я использую Docker, возникает ошибка, когда я пытаюсь зарегистрировать нового пользователя: токен истек.

Вопрос, который возникает: почему, когда я запускаю в своей IDE, все работает хорошо и никаких проблем не возникает.

application.yml:

spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL}  
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

server:
  port: 8083

application:
  security:
    jwt:
      secret-key: ${JWT_SECRET_KEY}
      expiration: 86400000000000000000
      refresh-token:
        expiration: 86400000000000000000

mail:
  host: smtp.gmail.com
  port: 587
  username: ${MAIL_USERNAME}
  password: ${MAIL_PASSWORD}
  properties:
    mail:
      smtp:
        auth: true
        starttls.enable: true

docker-compose.yml:

version: '3.8'
services:
  backend:
    image: communicationbackend
    ports:
      - "8083:8083"
    environment:
      SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
      SPRING_DATASOURCE_USERNAME: ${DB_USERNAME}  # Правильное имя переменной
      SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}  # Правильное имя переменной
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      MAIL_USERNAME: ${MAIL_USERNAME}
      MAIL_PASSWORD: ${MAIL_PASSWORD}
    depends_on:
      - db
    networks:
      - app-network
    env_file:
      - .env  # Загружает переменные окружения

  frontend:
    image: ramzi/frontend
    ports:
      - "82:80"
    networks:
      - app-network

  db:
    image: mysql:5.7
    environment:
      MYSQL_DATABASE: db-communication
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    ports:
      - "3307:3306"
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - app-network
    env_file:
      - .env

volumes:
  db_data:

networks:
  app-network:

application.yml:

spring:
  datasource:
    url: ${SPRING_DATASOURCE_URL}
    username: ${SPRING_DATASOURCE_USERNAME}
    password: ${SPRING_DATASOURCE_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQLDialect

server:
  port: 8083

application:
  security:
    jwt:
      secret-key: ${JWT_SECRET_KEY}
      expiration: 8640000000
      refresh-token:
        expiration: 864000000

mail:
  host: smtp.gmail.com
  port: 587
  username: ${MAIL_USERNAME}
  password: ${MAIL_PASSWORD}
  properties:
    mail:
      smtp:
        auth: true
        starttls.enable: true

.env:

# Переменные базы данных
DB_USERNAME=communication
DB_PASSWORD=password
SPRING_DATASOURCE_URL=jdbc:mysql://db:3306/db-communication

# Секрет JWT
JWT_SECRET_KEY=IS/A4dZ9K2z5tQSV3eGK2i4RGNdiD7qbGGTNq9xUZqO4pqa8+BIan+scnhFPz15pSFWkTGve4YNxWHXt90U5Tg==

# Настройки электронной почты
MAIL_USERNAME=****************
MAIL_PASSWORD=***************

Я запускаю свои контейнеры Docker, все работает. Я получаю доступ к своему фронтенду, он открывается, когда я должен зарегистрироваться:

Не удалось загрузить ресурс: сервер ответил статусом 403 ()
:8083/PlatformCommunication/v1/register:1 
       Не удалось загрузить ресурс: сервер ответил статусом 403 ()

Так как, пожалуйста, как это исправить?

jwtservice:

@Service
public class JwtService {

    @Value("${application.security.jwt.secret-key}")
    private String secretKey;  // Внедрение секретного ключа из application.yml

    @Value("${application.security.jwt.expiration}")
    private long jwtExpiration;

    @Value("${application.security.jwt.refresh-token.expiration}")
    private long refreshExpiration;

  public String extractUsername(String token) {
    return extractClaim(token, Claims::getSubject);
  }

  public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = extractAllClaims(token);
    return claimsResolver.apply(claims);
  }

  public String generateToken(UserDetails userDetails) {
    return generateToken(new HashMap<>(), userDetails);
  }

  public String generateToken(
          Map<String, Object> extraClaims,
          UserDetails userDetails
  ) {
    return buildToken(extraClaims, userDetails, jwtExpiration);
  }

  public String generateRefreshToken(
          UserDetails userDetails
  ) {
    return buildToken(new HashMap<>(), userDetails, refreshExpiration);
  }

  private String buildToken(
          Map<String, Object> extraClaims,
          UserDetails userDetails,
          long expiration
  ) {
    return Jwts
            .builder()
            .setClaims(extraClaims)
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date(System.currentTimeMillis()))
            .setExpiration(new Date(System.currentTimeMillis() + expiration))
            .signWith(getSignInKey(), SignatureAlgorithm.HS256)
            .compact();
  }

  public boolean isTokenValid(String token, UserDetails userDetails) {
    final String username = extractUsername(token);
    return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
  }

  public boolean isTokenExpired(String token) {
    return extractExpiration(token).before(new Date());
  }

  public Date extractExpiration(String token) {
    return extractClaim(token, Claims::getExpiration);
  }

  private Claims extractAllClaims(String token) {
    return Jwts
            .parserBuilder()
            .setSigningKey(getSignInKey())
            .build()
            .parseClaimsJws(token)
            .getBody();
  }

  private Key getSignInKey() {
    byte[] keyBytes = Decoders.BASE64.decode(secretKey);
    return Keys.hmacShaKeyFor(keyBytes);
  }
}

Ответ или решение

Решение проблемы: Исключение io.jsonwebtoken.ExpiredJwtException: JWT истек

Когда вы сталкиваетесь с исключением io.jsonwebtoken.ExpiredJwtException: JWT expired в Spring Boot приложении, которое работает с Angular и Docker, необходимо понимать, что это связано с механизмом работы JWT (JSON Web Token) и особенно с временем его жизни (expiration time). В данном случае ошибка возникает при попытке регистрации нового пользователя, и приложение работает корректно в локальной среде, но неработоспособно при запуске в контейнерах Docker.

Возможные причины проблемы и их решения

  1. Неправильное значение времени истечения (expiration) токена:
    В вашем файле application.yml вы указали время истечения токена как expiration: 86400000000000000000. Это значение крайне велико и может стать причиной того, что токен исчерпает время жизни до того, как будет использован. Проверьте, используется ли это значение в коде, когда вы генерируете токен. Обычно разумнее использовать значения в миллисекундах, например, 86400000 (24 часа).

    Рекомендация: Убедитесь, что значения expiration для JWT и refresh-токена заданы корректно:

    application:
     security:
       jwt:
         secret-key: ${JWT_SECRET_KEY}
         expiration: 86400000    # 1 день
         refresh-token:
           expiration: 86400000    # 1 день
  2. Разные окружения: локальное и Docker:
    Важно удостовериться, что переменные окружения загружаются корректно в контейнерах Docker. Если, к примеру, переменная JWT_SECRET_KEY не передается в контейнер, это может вызвать неправильное поведение вашего приложения. Проверьте, все ли переменные корректно указаны и загружаются из .env файла.

    Проверка: Запустите команду docker-compose config для контроля переменных окружения и их значений.

  3. Проблемы с временными зонами:
    При развертывании приложения в Docker важно учитывать временные зоны. Контейнер может находиться в другой временной зоне по сравнению с вашим локальным окружением, что может влиять на обработку времени. Вы можете получить проблемы, если время, установленное на контейнере, отличается от вашего локального времени.

    Решение: Установите конкретную временную зону для вашего контейнера:

    backend:
     image: communicationbackend
     environment:
       TZ: "Europe/Moscow"  # Укажите вашу временную зону
  4. Логи и отладка:
    Для лучшего понимания проблемы полезно анализировать логи вашего приложения, чтобы выявить, что именно происходит в момент, когда возникает исключение ExpiredJwtException. Убедитесь, что ваше приложение логирует детали о токенах, которые генерируются, и о их времени жизни.

  5. Поддержка и обновление зависимостей:
    Проверьте, используете ли вы последнюю версию библиотек для работы с JWT. Возможно, в старых версиях присутствуют ошибки, которые были устранены в более новых обновлениях.

Заключение

Ошибка JWT expired может быть вызвана множеством факторов, включая неправильные настройки токена, различные окружения и временные зоны. Правильная диагностика требует тщательной проверки конфигурации вашего приложения в Docker. Убедитесь, что ваши переменные окружения загружаются правильно, значения токена установлены корректно и учтены потенциальные проблемы с временными зонами. Используйте отладку и просмотр логов для более детального анализа ситуации.

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

Оцените материал
Добавить комментарий

Капча загружается...