← Назад к вопросам
Как происходит клиент серверное взаимодействие с JWT?
2.3 Middle🔥 231 комментариев
#REST API и HTTP#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Клиент-серверное взаимодействие с JWT
JWT позволяет реализовать безопасную аутентификацию в REST API с минимальным взаимодействием между клиентом и сервером. Разберём полный цикл взаимодействия.
Архитектура взаимодействия
Клиент (браузер/мобильное приложение)
↓
Сервер аутентификации (выдача токена)
↓
Остальные серверы API (валидация токена)
Шаг 1: Получение токена (Login)
Запрос клиента:
import requests
import json
# Клиент отправляет учетные данные
response = requests.post(
"https://api.example.com/api/v1/auth/login",
json={"username": "john", "password": "secret123"}
)
data = response.json()
token = data["access_token"] # JWT токен
local_storage["token"] = token # Сохраняем локально
Ответ сервера:
from fastapi import FastAPI, HTTPException
from datetime import datetime, timedelta, timezone
import jwt
app = FastAPI()
SECRET_KEY = "your-secret-key"
@app.post("/api/v1/auth/login")
async def login(username: str, password: str):
# Проверяем учетные данные
user = authenticate_user(username, password)
if not user:
raise HTTPException(status_code=401, detail="Invalid credentials")
# Создаём токен
payload = {
"sub": str(user.id), # subject (кто)
"username": user.username,
"role": user.role,
"exp": datetime.now(timezone.utc) + timedelta(hours=1), # когда закончится
"iat": datetime.now(timezone.utc) # когда создан
}
access_token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
return {
"access_token": access_token,
"token_type": "bearer",
"expires_in": 3600
}
Шаг 2: Отправка токена в каждом запросе
Клиент добавляет токен в заголовок Authorization:
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Все последующие запросы содержат токен
response = requests.get(
"https://api.example.com/api/v1/users/me",
headers=headers
)
В браузере (JavaScript/Fetch API):
const token = localStorage.getItem("token");
fetch("https://api.example.com/api/v1/users/me", {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(data => console.log(data));
Шаг 3: Валидация токена на сервере
Сервер проверяет подпись и данные:
from fastapi import Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthenticationCredentials
security = HTTPBearer()
async def get_current_user(credentials: HTTPAuthenticationCredentials = Depends(security)):
token = credentials.credentials
try:
# Декодируем и проверяем подпись
payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
user_id = payload.get("sub")
if not user_id:
raise HTTPException(status_code=401, detail="Invalid token")
return {"user_id": int(user_id), "role": payload.get("role")}
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token has expired")
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/api/v1/users/me")
async def get_current_user_info(current_user = Depends(get_current_user)):
user = await db.get_user(current_user["user_id"])
return {"id": user.id, "username": user.username, "role": user.role}
Шаг 4: Обновление токена (Refresh)
Когда access токен истекает, клиент использует refresh токен:
Клиент запрашивает новый access токен:
# Когда access_token истек
if response.status_code == 401: # Unauthorized
refresh_token = local_storage["refresh_token"]
response = requests.post(
"https://api.example.com/api/v1/auth/refresh",
json={"refresh_token": refresh_token}
)
if response.status_code == 200:
new_token = response.json()["access_token"]
local_storage["token"] = new_token
# Повторить исходный запрос
else:
# Refresh токен невалиден, требуется новый login
redirect_to_login()
Сервер выдаёт новый access токен:
@app.post("/api/v1/auth/refresh")
async def refresh_token(refresh_token: str, db: Session = Depends(get_db)):
try:
# Проверяем подпись refresh токена
payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=["HS256"])
user_id = payload.get("sub")
# Проверяем, что токен в БД (может быть отозван)
token_record = db.query(RefreshToken).filter(
RefreshToken.token == refresh_token,
RefreshToken.user_id == user_id,
RefreshToken.revoked == False
).first()
if not token_record:
raise HTTPException(status_code=401, detail="Invalid refresh token")
# Создаём новый access токен
new_payload = {
"sub": user_id,
"username": payload.get("username"),
"role": payload.get("role"),
"exp": datetime.now(timezone.utc) + timedelta(hours=1),
"iat": datetime.now(timezone.utc)
}
new_access_token = jwt.encode(new_payload, SECRET_KEY, algorithm="HS256")
return {"access_token": new_access_token, "token_type": "bearer"}
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="Invalid refresh token")
Полный цикл взаимодействия
# 1. ВХОД
POST /api/v1/auth/login
Body: {"username": "john", "password": "secret123"}
↓
Response: {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600
}
↓
# 2. ИСПОЛЬЗОВАНИЕ ТОКЕНА
GET /api/v1/users/me
Headers: {"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
↓
Response: {"id": 1, "username": "john", "role": "admin"}
↓
# 3. ИСТЕЧЕНИЕ И ОБНОВЛЕНИЕ
После 1 часа access_token истекает
↓
POST /api/v1/auth/refresh
Body: {"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
↓
Response: {"access_token": "новый_токен", "token_type": "bearer"}
↓
# 4. ВЫХОД
POST /api/v1/auth/logout
Headers: {"Authorization": "Bearer новый_токен"}
↓
Отозвать refresh_token в БД
Ответить 200 OK
CORS и JWT
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://frontend.example.com",
"http://localhost:3000"
],
allow_credentials=True, # Разрешить credentials (например, cookies)
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"], # Разрешить Authorization заголовок
max_age=3600
)
Безопасность при передаче
⚠️ Важные правила:
- Всегда использовать HTTPS — токены должны передаваться по защищённому каналу
- Никогда не логировать токены — они содержат конфиденциальную информацию
- Хранить токены безопасно:
- В браузере: httpOnly cookie или защищённое хранилище
- На мобильной платформе: keychain/keystore
# ❌ Плохо: логировать токен
logger.info(f"User logged in with token: {token}")
# ✅ Хорошо: логировать только user_id
logger.info(f"User {user_id} logged in successfully")
Разница между Access и Refresh токенами
| Параметр | Access Token | Refresh Token |
|---|---|---|
| Время жизни | 15 минут - 1 час | 7-30 дней |
| Хранилище | Memory/localStorage | Secure Storage |
| Использование | Все API запросы | Только для обновления |
| Отзыв | Не требуется в БД | Обязательно в БД |
| Передача | В заголовке Authorization | В body или cookie |
Этот подход обеспечивает безопасность (короткоживущие access токены), масштабируемость (stateless проверка) и удобство (автоматическое обновление).