JavaRush /Курсы /Модуль 4: FastAPI /Тестирование аутентификации через JWT: проверка токенов

Тестирование аутентификации через JWT: проверка токенов

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

JWT (JSON Web Token) — это сердце многих современных систем аутентификации.

Они позволяют нам убедиться, что пользователи имеют доступ только к разрешённым ресурсам, но дают и простор для ошибок:

  • Токен может быть недействительным или истёкшим.
  • Пользователь может попытаться подделать токен.
  • Неавторизованный запрос должен корректно возвращать ошибку 401 Unauthorized.

Качественное тестирование JWT гарантирует, что аутентификация работает предсказуемо, безопасно и эффективно.


Что будем тестировать?

Защищённые эндпоинты в FastAPI требуют:

  1. Генерации токена при успешной аутентификации.
  2. Проверки валидности токена.
  3. Ограничения доступа к защищённым ресурсам без токена или с неверным токеном.

Наша задача — протестировать эти сценарии.


Подготовка к тестированию

Перед тем как писать тесты, нужно убедиться, что у нас есть рабочая аутентификация через JWT в нашем FastAPI-приложении.

Давайте создадим небольшую базовую реализацию.

Давайте приведём простейший пример аутентификации через JWT.

Вот как может выглядеть базовый код для генерации и проверки JWT:


from fastapi import FastAPI, HTTPException, Depends
from fastapi.security import OAuth2PasswordBearer
from jose import jwt, JWTError
from datetime import datetime, timedelta

app = FastAPI()

SECRET_KEY = "supersecretkey"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)

def verify_token(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/token")
async def login(username: str, password: str):
    # Считаем, что любой username/password валидны для простоты (не делайте так в проде!)
    access_token = create_access_token({"sub": username})
    return {"access_token": access_token, "token_type": "bearer"}

@app.get("/protected")
async def protected_route(token: str = Depends(oauth2_scheme)):
    verify_token(token)
    return {"message": "You have access!"}

Этот код:

  • Генерирует JWT при логине (/token).
  • Проверяет JWT при доступе к защищённым ресурсам (/protected).

Установка зависимостей

Для работы с JWT и тестирования понадобятся дополнительные библиотеки:


pip install python-jose
pip install pytest pytest-asyncio httpx

Теперь мы готовы к тестам!


Тестирование генерации токена

Первый шаг — убедиться, что /token возвращает доступный JWT-токен. Напишем тест:


from fastapi.testclient import TestClient
from main import app  # Импортируем приложение FastAPI

client = TestClient(app)

def test_generate_token():
    response = client.post("/token", data={"username": "user", "password": "pass"})
    assert response.status_code == 200
    json_data = response.json()
    assert "access_token" in json_data
    assert "token_type" in json_data
    assert json_data["token_type"] == "bearer"

Здесь мы:

  1. Отправляем POST-запрос на /token.
  2. Проверяем, что возвращается статус 200.
  3. Убеждаемся, что в ответе есть access_token и token_type.

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

После получения токена нужно убедиться, что он работает.

Мы отправим запрос к защищённому эндпоинту /protected с токеном в заголовке.


def test_valid_token_access():
    # Получаем токен
    response = client.post("/token", data={"username": "user", "password": "pass"})
    token = response.json().get("access_token")
    
    # Используем токен для доступа к защищённому маршруту
    headers = {"Authorization": f"Bearer {token}"}
    protected_response = client.get("/protected", headers=headers)
    assert protected_response.status_code == 200
    assert protected_response.json() == {"message": "You have access!"}

Здесь мы:

  1. Логинимся и получаем токен.
  2. Используем его в заголовке Authorization.
  3. Убеждаемся, что доступ к /protected разрешён и возвращает правильный ответ.

Тестирование недействительных токенов

Теперь проверим, что API корректно обрабатывает случаи с отсутствием или поддельным токеном.

1. Без токена:


def test_no_token_access():
    response = client.get("/protected")
    assert response.status_code == 401
    assert response.json() == {"detail": "Not authenticated"}

Здесь мы посылаем запрос без токена. Ожидаем статус 401 и сообщение об ошибке.

2. С поддельным токеном:


def test_invalid_token_access():
    headers = {"Authorization": "Bearer invalidtoken"}
    response = client.get("/protected", headers=headers)
    assert response.status_code == 401
    assert response.json() == {"detail": "Invalid token"}

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


Тестирование истечения срока действия токена

JWT имеет срок действия, после которого он становится недействительным.

Давайте протестируем этот случай.

Для этого нужно изменить срок действия токена на что-то короткое (например, 1 секунду).

Обновляем функцию create_access_token:


ACCESS_TOKEN_EXPIRE_MINUTES = 0.001  # 1 секунда

Теперь тест:


import time

def test_expired_token_access():
    response = client.post("/token", data={"username": "user", "password": "pass"})
    token = response.json().get("access_token")
    
    # Ждём истечения срока действия токена
    time.sleep(2)
    
    headers = {"Authorization": f"Bearer {token}"}
    response = client.get("/protected", headers=headers)
    assert response.status_code == 401
    assert response.json() == {"detail": "Invalid token"}

Мы:

  1. Генерируем токен.
  2. Ждём истечения его срока действия.
  3. Проверяем, что доступ запрещён.

Общая структура тестов

Вот как выглядит структура всех тестов:


tests/
├── test_authentication.py

Файл test_authentication.py содержит тесты для:

  • Генерации токена.
  • Доступа с валидным токеном.
  • Доступа без токена.
  • Доступа с поддельным токеном.
  • Истечения срока действия токена.

Заключительные мысли на сегодня

Как видите, тестирование аутентификации через JWT включает много полезных сценариев, которые легко автоматизировать с помощью Pytest. Эти тесты не только подтверждают корректность вашей реализации, но и дают уверенность в безопасности приложения. Теперь вы готовы защищать свои API, как настоящий серверный супергерой!

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