JavaRush /Курсы /Модуль 4: FastAPI /Работа с токенами: создание и проверка валидности

Работа с токенами: создание и проверка валидности

Модуль 4: FastAPI
4 уровень , 6 лекция
Открыта

По сути, токены — это ключи доступа. Они позволяют вашему приложению понять: кто вы, какие права у вас есть, и ещё убедиться, что вы не злоумышленник. Это особенно важно для API-запросов, где сервер не хранит состояние между запросами, а о вас знает только по переданному токену.

Представьте, что вы пытаетесь попасть в закрытый клуб. Без ключа (читай: токена) вы просто останетесь за дверью. С правильным ключом, но просроченным, вас попросят обновить его (как рефреш-токен). Наша задача — сделать этот процесс автоматическим, но безопасным.


Устройство токенов

Вспомним, как выглядит JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiZXhwIjoxNjY3OTEyMzQ1fQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Он состоит из трёх частей:

  1. Header — шапка токена, указывающая на алгоритм подписи (например, HMAC SHA256).
  2. Payload — полезные данные (id пользователя, время истечения, права доступа).
  3. Signature — подпись, чтобы никто не мог изменить данные в payload.

Подробное объяснение этой структуры мы уже разбирали, так что перейдём к практике.


Генерация токенов

Для начала нам нужна библиотека PyJWT. Если вы её ещё не установили, сделайте это:

pip install pyjwt

Пусть у нас есть пользователь с уникальным user_id. Мы создадим для него токен, который будет действителен в течение часа.


import jwt
from datetime import datetime, timedelta

# Секретный ключ для подписи токенов (должен быть защищённым!)
SECRET_KEY = "supersecretkey"

def create_access_token(user_id: int):
    # Устанавливаем время истечения (через 1 час)
    expiration = datetime.utcnow() + timedelta(hours=1)

    # Собираем payload
    payload = {
        "sub": user_id,        # Уникальный идентификатор пользователя
        "exp": expiration      # Время истечения токена
    }

    # Генерируем JWT
    token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
    return token

# Пример использования
token = create_access_token(user_id=42)
print(f"Access token: {token}")

Этот токен можно передавать клиенту. Клиент, в свою очередь, будет отправлять его в заголовке Authorization при запросах к нашему API.

Пара слов о безопасности

Ваш SECRET_KEY — это самое ценное для подписи и проверки токенов. Если он утечёт, токены можно будет подделать. Храните его в переменной окружения или в специальном менеджере секретов (например, Vault).


Проверка и валидность токенов

Теперь научимся проверять токены, которые передаются в запросах. Мы будем декодировать их и проверять:

  1. Подпись токена.
  2. Срок действия (expiration).

Базовый пример проверки токена


def verify_access_token(token: str):
    try:
        # Раскодируем токен
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        return payload  # Возвращаем полезные данные из токена
    except jwt.ExpiredSignatureError:
        raise Exception("Токен истёк")
    except jwt.InvalidTokenError:
        raise Exception("Недействительный токен")

# Пример использования
try:
    payload = verify_access_token(token)
    print(f"Token payload: {payload}")
except Exception as e:
    print(f"Ошибка проверки токена: {e}")

Работа с токенами в FastAPI

Давайте внедрим всё это в приложение на FastAPI. Мы защитим эндпоинты с помощью токенов. Для этого потребуется:

  1. Генерация токенов при входе пользователя.
  2. Проверка токена для доступа к защищённым маршрутам.

Мы создадим эндпоинт /login, который выдаёт токен после проверки учётных данных пользователя.


from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordRequestForm

app = FastAPI()

# Фейковая база данных
users_db = {"user@example.com": {"password": "password123", "id": 1}}

@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
    user = users_db.get(form_data.username)
    if not user or user["password"] != form_data.password:
        raise HTTPException(status_code=400, detail="Неверные учетные данные")
    
    access_token = create_access_token(user_id=user["id"])
    return {"access_token": access_token, "token_type": "bearer"}

Теперь клиент отправляет данные учётной записи (логин и пароль) и получает токен доступа.

Защита эндпоинтов

Чтобы защитить API, мы будем проверять токен перед выполнением запроса. FastAPI предоставляет класс OAuth2PasswordBearer, который извлекает токен из заголовка Authorization.


from fastapi.security import OAuth2PasswordBearer

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/login")

@app.get("/protected")
def protected_route(token: str = Depends(oauth2_scheme)):
    payload = verify_access_token(token)
    return {"message": f"Привет, пользователь {payload['sub']}!"}

Если токен истёк или недействителен, пользователь получит ошибку.


Работа с рефреш-токенами

Рефреш-токены позволяют обновлять токены доступа без необходимости входа в систему. Схема работы:

  1. Пользователь получает два токена: access_token и refresh_token.
  2. Когда access_token истекает, клиент отправляет refresh_token для получения нового access_token.

Реализация


def create_refresh_token(user_id: int):
    expiration = datetime.utcnow() + timedelta(days=7)
    payload = {
        "sub": user_id,
        "exp": expiration
    }
    return jwt.encode(payload, SECRET_KEY, algorithm="HS256")

@app.post("/refresh")
def refresh_token(refresh_token: str):
    try:
        payload = verify_access_token(refresh_token)
        new_access_token = create_access_token(user_id=payload["sub"])
        return {"access_token": new_access_token, "token_type": "bearer"}
    except Exception as e:
        raise HTTPException(status_code=401, detail="Неверный refresh токен")

Типичные ошибки и их предотвращение

  1. Истёкшие токены. При проверке токена можно получить ошибку jwt.ExpiredSignatureError. Это нормальное поведение, так как срок действия токена уже истёк. Добавьте обработчик этой ошибки, чтобы клиент понимал, что нужно обновить токен.
  2. Неверные токены. Если токен был подделан или повреждён, библиотека выбрасывает jwt.InvalidTokenError. Не забудьте аккуратно обрабатывать эту ошибку.
  3. Безопасность ключей. Никогда не захардкодивайте секретный ключ в коде. Используйте переменные окружения.

Этот подход к работе с токенами позволяет строить надёжные и безопасные системы аутентификации. Закрепите материал, добавляя токены в ваши тестовые API и проверяя их на практических кейсах.

1
Задача
Модуль 4: FastAPI, 4 уровень, 6 лекция
Недоступна
Проверка валидности токена
Проверка валидности токена
1
Задача
Модуль 4: FastAPI, 4 уровень, 6 лекция
Недоступна
Защищённый эндпоинт FastAPI
Защищённый эндпоинт FastAPI
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ