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

В чем разница между сессией и токеном?

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"]}

Процесс:

  1. Клиент отправляет логин и пароль
  2. Сервер проверяет, создаёт сессию и сохраняет в памяти
  3. Клиент получает cookie с ID сессии
  4. При каждом запросе браузер автоматически отправляет эту cookie
  5. Сервер ищет сессию и проверяет пользователя

Что такое токен?

Токен — это самоописывающаяся строка, которая содержит всю необходимую информацию и проверяется криптографически. Сервер не хранит токены, только проверяет их подпись.

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 в разработке очень популярен, но нужно понимать его ограничения
В чем разница между сессией и токеном? | PrepBro