← Назад к вопросам
В чем разница между сессией и токеном?
1.8 Middle🔥 211 комментариев
#REST API и HTTP#Безопасность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между сессией и токеном
Это фундаментальное различие в архитектуре аутентификации веб-приложений. Выбор между ними зависит от архитектуры приложения, масштаба и требований к безопасности.
Что такое сессия?
Сессия — это сохраняемое на сервере состояние пользователя. Сервер хранит информацию о пользователе и выдаёт клиенту только идентификатор сессии (обычно в cookie).
from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = "secret"
@app.route("/login", methods=["POST"])
def login():
user_id = request.json.get("user_id")
# Создаём сессию НА СЕРВЕРЕ
session["user_id"] = user_id # ← Хранится на сервере
session["username"] = "john"
# Клиенту отправляем только ID сессии
return {"message": "Logged in"}
@app.route("/me")
def get_me():
# Проверяем сессию НА СЕРВЕРЕ
if "user_id" not in session: # ← Ищем на сервере
return {"error": "Not authenticated"}, 401
return {"user_id": session["user_id"]}
Процесс:
- Клиент отправляет логин и пароль
- Сервер проверяет, создаёт сессию и сохраняет в памяти
- Клиент получает cookie с ID сессии
- При каждом запросе браузер автоматически отправляет эту cookie
- Сервер ищет сессию и проверяет пользователя
Что такое токен?
Токен — это самоописывающаяся строка, которая содержит всю необходимую информацию и проверяется криптографически. Сервер не хранит токены, только проверяет их подпись.
from fastapi import FastAPI, Depends, HTTPException
from jose import JWTError, jwt
from datetime import datetime, timedelta
app = FastAPI()
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
@app.post("/login")
def login():
user_id = 1
# Создаём токен (НЕ сохраняется на сервере)
payload = {
"sub": str(user_id),
"username": "john",
"exp": datetime.utcnow() + timedelta(hours=1) # Время жизни
}
# Подписываем токен секретным ключом
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return {"access_token": token, "token_type": "bearer"}
@app.get("/me")
def get_me(token: str = Depends(oauth2_scheme)):
try:
# Проверяем подпись токена
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
if user_id is None:
raise HTTPException(status_code=401, detail="Invalid token")
return {"user_id": user_id, "username": payload.get("username")}
except JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
Основные различия
| Параметр | Сессия | Токен |
|---|---|---|
| Место хранения | На сервере (памяти, БД) | На клиенте |
| Проверка | Поиск в памяти/БД | Криптографическая подпись |
| Масштабируемость | Плохо (требует общего хранилища) | Хорошо (без состояния на сервере) |
| Отзыв | Мгновенный (удаляем из памяти) | Сложный (нужно список отозванных) |
| Передача | HTTP cookie (автоматически) | HTTP header (Authorization) |
| Размер | Маленький (просто ID) | Большой (содержит данные) |
| CSRF защита | Нужна (same-site cookie) | Не нужна (отправляется явно) |
| Мобильные приложения | Неудобно | Идеально |
Процесс с сессией
1. Клиент: POST /login {"password": "123"}
↓
2. Сервер: Проверяю пароль → создаю сессию → сохраняю в памяти
↓
3. Сервер: SET-COOKIE: session_id=abc123
↓
4. Браузер: Сохраняет cookie
↓
5. Клиент: GET /me + Cookie: session_id=abc123
↓
6. Сервер: Ищу сессию в памяти → нашёл → возвращаю данные
Процесс с токеном
1. Клиент: POST /login {"password": "123"}
↓
2. Сервер: Проверяю пароль → создаю токен → подписываю
↓
3. Сервер: {"access_token": "eyJhbGc...", "token_type": "bearer"}
↓
4. Клиент: Сохраняет токен в localStorage/sessionStorage
↓
5. Клиент: GET /me + Authorization: Bearer eyJhbGc...
↓
6. Сервер: Проверяю подпись → валиден → возвращаю данные
Практический пример: сессия
from flask import Flask, session, jsonify
from datetime import datetime
app = Flask(__name__)
app.secret_key = "my-secret"
# Хранилище сессий (в реальном приложении это Redis или БД)
sessions_storage = {}
@app.route("/login", methods=["POST"])
def login():
user_id = 1
# Создаём сессию НА СЕРВЕРЕ
session_id = "session_abc123"
sessions_storage[session_id] = {
"user_id": user_id,
"username": "john",
"created_at": datetime.now(),
"last_activity": datetime.now()
}
# Отправляем cookie
response = jsonify({"message": "Logged in"})
response.set_cookie("session_id", session_id, httponly=True)
return response
@app.route("/me")
def get_me():
# Получаем ID сессии из cookie
session_id = request.cookies.get("session_id")
# Ищем сессию НА СЕРВЕРЕ
if session_id not in sessions_storage:
return {"error": "Not authenticated"}, 401
session_data = sessions_storage[session_id]
return {"user_id": session_data["user_id"]}
@app.route("/logout", methods=["POST"])
def logout():
session_id = request.cookies.get("session_id")
# Удаляем сессию СРАЗУ (отзыв мгновенный)
if session_id in sessions_storage:
del sessions_storage[session_id]
return {"message": "Logged out"}
Практический пример: токен (JWT)
from fastapi import FastAPI, Depends, HTTPException
from fastapi.security import HTTPBearer, HTTPAuthCredentials
from jose import jwt
from datetime import datetime, timedelta
app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "my-secret"
ALGORITHM = "HS256"
@app.post("/login")
def login():
user_id = 1
# Создаём токен (НЕ сохраняется)
payload = {
"sub": str(user_id),
"username": "john",
"exp": datetime.utcnow() + timedelta(hours=1)
}
# Подписываем
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return {"access_token": token, "token_type": "bearer"}
@app.get("/me")
def get_me(credentials: HTTPAuthCredentials = Depends(security)):
token = credentials.credentials
try:
# Проверяем подпись (НЕ ищем на сервере)
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
user_id = payload.get("sub")
return {"user_id": user_id, "username": payload.get("username")}
except jwt.ExpiredSignatureError:
raise HTTPException(status_code=401, detail="Token expired")
except jwt.JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
# Отзыв токена: добавляем в чёрный список
blacklisted_tokens = set()
@app.post("/logout")
def logout(credentials: HTTPAuthCredentials = Depends(security)):
token = credentials.credentials
# Добавляем в чёрный список (но это медленнее, чем удаление сессии)
blacklisted_tokens.add(token)
return {"message": "Logged out"}
@app.get("/me-safe")
def get_me_safe(credentials: HTTPAuthCredentials = Depends(security)):
token = credentials.credentials
# Проверяем чёрный список
if token in blacklisted_tokens:
raise HTTPException(status_code=401, detail="Token revoked")
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return {"user_id": payload.get("sub")}
except jwt.JWTError:
raise HTTPException(status_code=401, detail="Invalid token")
Когда использовать сессию?
Используй сессию, если:
- Традиционное веб-приложение (MPA с шаблонами)
- Нужна возможность мгновенного отзыва
- Всё работает на одном сервере
- Нет микросервисов
Когда использовать токен?
Используй токен (JWT), если:
- SPA или мобильное приложение
- Микросервисная архитектура
- Нужна стателесность (статель на сервере)
- API используется несколькими клиентами
- Есть CORS запросы
Гибридный подход: Refresh Token
from fastapi import FastAPI
from datetime import datetime, timedelta
from jose import jwt
app = FastAPI()
SECRET_KEY = "secret"
ACCESS_TOKEN_EXPIRE = 15 # минут
REFRESH_TOKEN_EXPIRE = 7 # дней
@app.post("/login")
def login():
# Короткоживущий access token
access_payload = {
"sub": "user_id",
"exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE)
}
access_token = jwt.encode(access_payload, SECRET_KEY)
# Долгоживущий refresh token
refresh_payload = {
"sub": "user_id",
"exp": datetime.utcnow() + timedelta(days=REFRESH_TOKEN_EXPIRE)
}
refresh_token = jwt.encode(refresh_payload, SECRET_KEY)
return {
"access_token": access_token,
"refresh_token": refresh_token,
"token_type": "bearer"
}
@app.post("/refresh")
def refresh(refresh_token: str):
try:
payload = jwt.decode(refresh_token, SECRET_KEY)
# Создаём новый access token
new_payload = {
"sub": payload["sub"],
"exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE)
}
new_access_token = jwt.encode(new_payload, SECRET_KEY)
return {"access_token": new_access_token}
except jwt.JWTError:
raise HTTPException(status_code=401, detail="Invalid refresh token")
Проблема с масштабированием: сессия
# С двумя серверами: ПРОБЛЕМА!
# Сервер 1 создал сессию для пользователя
# Пользователь отправляет запрос на Сервер 2
# Сервер 2 не знает эту сессию → 401 Unauthorized
# Решение: Redis (общее хранилище сессий)
from redis import Redis
redis = Redis()
@app.post("/login")
def login():
session_id = "session_123"
redis.set(session_id, json.dumps({"user_id": 1}))
return {"session_id": session_id}
Итоговый вывод
- Сессия — сохраняется на сервере, можно отозвать мгновенно, плохо масштабируется
- Токен — самопроверяемый, стателесный, хорош для масштабирования, сложнее отозвать
- Для SPA и API обычно используют JWT токены
- Для традиционных веб-приложений используют сессии
- Refresh token — гибридный подход, объединяет лучшее из обоих
- JWT в разработке очень популярен, но нужно понимать его ограничения