Опишите процесс аутентификации клиента на сервере с помощью JWT

«Опишите процесс аутентификации клиента на сервере с помощью JWT» — вопрос из категории Безопасность, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Процесс аутентификации с использованием JSON Web Token (JWT) — это stateless-механизм, который позволяет серверу проверять подлинность запросов без хранения сессионных данных.

Алгоритм взаимодействия:

  1. Аутентификация: Клиент отправляет свои учетные данные (например, логин и пароль) на специальный эндпоинт сервера (например, /login).
  2. Генерация и выдача токена: Сервер проверяет учетные данные. В случае успеха он создает JWT, который содержит полезную нагрузку (payload) с информацией о пользователе (например, user_id) и метаданными (например, срок действия токена exp). Токен подписывается секретным ключом, известным только серверу.
  3. Хранение токена: Сервер отправляет сгенерированный JWT клиенту. Клиент должен сохранить его (например, в localStorage, sessionStorage или HttpOnly cookie).
  4. Авторизация запросов: При каждом последующем запросе к защищенным ресурсам клиент должен включать JWT в заголовок Authorization со схемой Bearer.
    Authorization: Bearer <your_jwt_token>
  5. Верификация на сервере: Сервер получает токен из заголовка, проверяет его подпись с помощью своего секретного ключа и валидность (например, не истек ли срок действия). Если все проверки пройдены, сервер обрабатывает запрос.

Пример на Python с PyJWT:

import jwt
import datetime
from functools import wraps
from flask import Flask, request, jsonify

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-super-secret-key'

# --- Генерация токена при логине ---
@app.route('/login', methods=['POST'])
def login():
    # Здесь должна быть реальная проверка пользователя
    # ...

    # Создаем токен со сроком жизни 30 минут
    token = jwt.encode({
        'user_id': 123,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
    }, app.config['SECRET_KEY'], algorithm="HS256")

    return jsonify({'token': token})

# --- Декоратор для защиты эндпоинтов ---
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = None
        if 'Authorization' in request.headers:
            token = request.headers['Authorization'].split(" ")[1]

        if not token:
            return jsonify({'message': 'Токен отсутствует!'}), 401

        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=["HS256"])
            current_user_id = data['user_id']
        except jwt.ExpiredSignatureError:
            return jsonify({'message': 'Срок действия токена истек!'}), 401
        except jwt.InvalidTokenError:
            return jsonify({'message': 'Невалидный токен!'}), 401

        return f(current_user_id, *args, **kwargs)
    return decorated

# --- Защищенный эндпоинт ---
@app.route('/protected')
@token_required
def protected_route(current_user_id):
    return jsonify({'message': f'Доступ разрешен для пользователя {current_user_id}'})