← Назад к вопросам

Как определяется срок жизни JWT-токена?

1.0 Junior🔥 71 комментариев
#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Срок жизни JWT-токена

JWT (JSON Web Token) содержит информацию о своём сроке действия в виде claims. Рассмотрю как это работает и как управлять временем жизни.

Структура JWT

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

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  1. Header: алгоритм и тип токена
  2. Payload: данные (claims)
  3. Signature: подпись

Payload декодируется в:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516239922
}

Claims для управления сроком

В JWT используются стандартные claims:

ClaimЗначениеПример
iat (issued at)Время создания1516239022
exp (expiration time)Время истечения1516239922
nbf (not before)С какого времени действует1516239022
jti (JWT ID)Уникальный ID токена"abc123"

Способ 1: Создание JWT с exp в Python

Использование PyJWT:

pip install PyJWT
import jwt
import datetime
from datetime import timedelta

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

def create_token(user_id: int, expires_in_hours: int = 1) -> str:
    """Создаёт JWT с определённым сроком действия."""
    
    # Текущее время + указанное количество часов
    expiration = datetime.datetime.utcnow() + timedelta(hours=expires_in_hours)
    
    payload = {
        "user_id": user_id,
        "exp": expiration,  # Время истечения
        "iat": datetime.datetime.utcnow(),  # Время создания
        "nbf": datetime.datetime.utcnow()   # С какого момента действует
    }
    
    token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
    return token

# Использование
token = create_token(user_id=123, expires_in_hours=24)
print(token)

Способ 2: Проверка JWT и exp

import jwt
from datetime import datetime

def verify_token(token: str) -> dict:
    """Проверяет валидность токена и его срок."""
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        
        # Проверка exp автоматическая в decode()
        # Если токен истёк, выбросится jwt.ExpiredSignatureError
        
        return payload
    
    except jwt.ExpiredSignatureError:
        raise ValueError("Token has expired")
    
    except jwt.InvalidTokenError as e:
        raise ValueError(f"Invalid token: {e}")

# Использование
try:
    payload = verify_token(token)
    print(f"User ID: {payload['user_id']}")
except ValueError as e:
    print(f"Error: {e}")

Способ 3: Refresh токены

Длительные токены опасны. Используют pair tokens:

from typing import Tuple

def create_token_pair(user_id: int) -> Tuple[str, str]:
    """Создаёт access token (15 мин) и refresh token (7 дней)."""
    
    # Access token — короткоживущий
    access_exp = datetime.datetime.utcnow() + timedelta(minutes=15)
    access_payload = {
        "user_id": user_id,
        "type": "access",
        "exp": access_exp,
        "iat": datetime.datetime.utcnow()
    }
    access_token = jwt.encode(access_payload, SECRET_KEY, algorithm=ALGORITHM)
    
    # Refresh token — длительный
    refresh_exp = datetime.datetime.utcnow() + timedelta(days=7)
    refresh_payload = {
        "user_id": user_id,
        "type": "refresh",
        "exp": refresh_exp,
        "iat": datetime.datetime.utcnow()
    }
    refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm=ALGORITHM)
    
    return access_token, refresh_token

# Использование
access_token, refresh_token = create_token_pair(user_id=123)
print(f"Access: {access_token}")
print(f"Refresh: {refresh_token}")

Обновление access токена:

def refresh_access_token(refresh_token: str) -> str:
    """Выдаёт новый access token по refresh токену."""
    try:
        payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
        
        if payload.get("type") != "refresh":
            raise ValueError("Invalid token type")
        
        # Выдаём новый access token
        user_id = payload["user_id"]
        return create_token(user_id, expires_in_hours=0.25)  # 15 минут
    
    except jwt.ExpiredSignatureError:
        raise ValueError("Refresh token has expired")

# Использование
new_access_token = refresh_access_token(refresh_token)

Способ 4: FastAPI с JWT

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthCredentials
import jwt
from datetime import datetime, timedelta

app = FastAPI()
security = HTTPBearer()

SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"

class TokenData:
    user_id: int
    exp: datetime

def create_access_token(user_id: int):
    """Создаёт access token."""
    exp = datetime.utcnow() + timedelta(minutes=15)
    payload = {
        "user_id": user_id,
        "exp": exp
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(credentials: HTTPAuthCredentials = Depends(security)):
    """Проверяет токен из заголовка Authorization."""
    token = credentials.credentials
    
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id = payload.get("user_id")
        
        if user_id is None:
            raise HTTPException(status_code=401, detail="Invalid token")
        
        return user_id
    
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token has expired")
    
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/login")
async def login(username: str, password: str):
    # Проверка credentials
    if username == "user" and password == "pass":
        token = create_access_token(user_id=1)
        return {"access_token": token, "token_type": "bearer"}
    
    raise HTTPException(status_code=401, detail="Invalid credentials")

@app.get("/protected")
async def protected_route(user_id: int = Depends(verify_token)):
    return {"message": f"Hello user {user_id}"}

Способ 5: Специальные сроки

def create_token_with_custom_expiry(user_id: int, expires_at: datetime) -> str:
    """Создаёт токен с конкретной датой истечения."""
    payload = {
        "user_id": user_id,
        "exp": expires_at,
        "iat": datetime.utcnow()
    }
    return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

# Использование
specific_time = datetime(2025, 12, 31, 23, 59, 59)
token = create_token_with_custom_expiry(user_id=123, expires_at=specific_time)

Способ 6: Проверка оставшегося времени

import time

def get_token_ttl(token: str) -> int:
    """Возвращает количество секунд до истечения токена."""
    try:
        # decode с verify=False не проверяет подпись
        payload = jwt.decode(token, options={"verify_signature": False})
        
        exp_timestamp = payload.get("exp")
        if not exp_timestamp:
            return -1
        
        now = time.time()
        ttl = int(exp_timestamp - now)
        
        return max(ttl, 0)  # Не меньше 0
    
    except jwt.DecodeError:
        return -1

# Использование
ttl = get_token_ttl(token)
print(f"Token expires in {ttl} seconds")

if ttl < 300:  # Менее 5 минут
    print("Token is about to expire, consider refreshing")

Рекомендации по срокам

Тип токенаРекомендуемый срокПричина
Access token5-15 минутЗащита при компрометации
Refresh token7-30 днейУдобство пользователя
API token1-6 месяцевДолгосрочный доступ для приложений
Восстановления пароля1 часБезопасность
Email подтверждение24 часаУдобство

Лучшие практики

  • Всегда устанавливайте exp — это критично
  • Используйте короткие сроки для access токенов (15 минут)
  • Используйте refresh токены для получения нового access
  • Храните refresh токены в httpOnly cookies для защиты от XSS
  • Проверяйте не только exp, но и iat для защиты от старых токенов
  • Используйте jti (JWT ID) для отслеживания и blacklist'ов
  • Не доверяй только клиенту — всегда проверяй на сервере

Срок жизни JWT должен балансировать между безопасностью и удобством.