Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Срок жизни JWT-токена
JWT (JSON Web Token) содержит информацию о своём сроке действия в виде claims. Рассмотрю как это работает и как управлять временем жизни.
Структура JWT
JWT состоит из трёх частей, разделённых точками:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- Header: алгоритм и тип токена
- Payload: данные (claims)
- 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 token | 5-15 минут | Защита при компрометации |
| Refresh token | 7-30 дней | Удобство пользователя |
| API token | 1-6 месяцев | Долгосрочный доступ для приложений |
| Восстановления пароля | 1 час | Безопасность |
| Email подтверждение | 24 часа | Удобство |
Лучшие практики
- Всегда устанавливайте
exp— это критично - Используйте короткие сроки для access токенов (15 минут)
- Используйте refresh токены для получения нового access
- Храните refresh токены в httpOnly cookies для защиты от XSS
- Проверяйте не только exp, но и iat для защиты от старых токенов
- Используйте
jti(JWT ID) для отслеживания и blacklist'ов - Не доверяй только клиенту — всегда проверяй на сервере
Срок жизни JWT должен балансировать между безопасностью и удобством.