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

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

Модуль 4: FastAPI
Рівень 5 , Лекція 6
Відкрита

JWT (JSON Web Token) — це серце багатьох сучасних систем автентифікації.

Вони дозволяють переконатися, що користувачі мають доступ тільки до дозволених ресурсів, але водночас відкривають місце для помилок:

  • Токен може бути недійсним або простроченим.
  • Користувач може спробувати підробити токен.
  • Неавторизований запит має коректно повертати помилку 401 Unauthorized.

Якісне тестування JWT гарантує, що автентифікація працює передбачувано, безпечно й ефективно.


Що будемо тестувати?

Захищені endpoints в 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.

Тестування валідності токена

Після отримання токена треба переконатися, що він працює.

Ми відправимо запит до захищеного endpoint'у /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, як справжній серверний супергерой!

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ