Вопрос или проблема
У меня есть приложение 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.
Возможные причины проблемы и их решения
-
Неправильное значение времени истечения (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 день
-
Разные окружения: локальное и Docker:
Важно удостовериться, что переменные окружения загружаются корректно в контейнерах Docker. Если, к примеру, переменнаяJWT_SECRET_KEY
не передается в контейнер, это может вызвать неправильное поведение вашего приложения. Проверьте, все ли переменные корректно указаны и загружаются из.env
файла.Проверка: Запустите команду
docker-compose config
для контроля переменных окружения и их значений. -
Проблемы с временными зонами:
При развертывании приложения в Docker важно учитывать временные зоны. Контейнер может находиться в другой временной зоне по сравнению с вашим локальным окружением, что может влиять на обработку времени. Вы можете получить проблемы, если время, установленное на контейнере, отличается от вашего локального времени.Решение: Установите конкретную временную зону для вашего контейнера:
backend: image: communicationbackend environment: TZ: "Europe/Moscow" # Укажите вашу временную зону
-
Логи и отладка:
Для лучшего понимания проблемы полезно анализировать логи вашего приложения, чтобы выявить, что именно происходит в момент, когда возникает исключениеExpiredJwtException
. Убедитесь, что ваше приложение логирует детали о токенах, которые генерируются, и о их времени жизни. -
Поддержка и обновление зависимостей:
Проверьте, используете ли вы последнюю версию библиотек для работы с JWT. Возможно, в старых версиях присутствуют ошибки, которые были устранены в более новых обновлениях.
Заключение
Ошибка JWT expired
может быть вызвана множеством факторов, включая неправильные настройки токена, различные окружения и временные зоны. Правильная диагностика требует тщательной проверки конфигурации вашего приложения в Docker. Убедитесь, что ваши переменные окружения загружаются правильно, значения токена установлены корректно и учтены потенциальные проблемы с временными зонами. Используйте отладку и просмотр логов для более детального анализа ситуации.
Следуя этому подходу, вы сможете устранить проблему и вернуть свое приложение к нормальной работе как в локальном окружении, так и в Docker.