JavaRush /Курси /Модуль 4: FastAPI /Захист ендпоінтів через JWT: обмеження доступу

Захист ендпоінтів через JWT: обмеження доступу

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

Уявіть, що ваш API — це такий собі клуб, і тільки учасники з правильно виданими перепустками можуть туди потрапити. JWT у цьому випадку — наш "перепустка". Без верифікації токена будь-хто може отримати доступ до ваших даних, наче хтось пробрався до вашого клубу без запрошення. Щоб такого не сталося, ми захищаємо ендпоінти, перевіряючи токен при кожному запиті.

Використання JWT дозволяє:

  • Забезпечити безпеку, обмеживши доступ неавторизованим користувачам.
  • Реалізувати контроль доступу на основі ролей, якщо потрібно.
  • Зробити API зручним для інтеграції, бо токени не вимагають зберігання стану на сервері.

Основи захисту ендпоінтів

Захист ендпоінтів складається з двох ключових операцій:

  1. Перевірка JWT: переконатися, що наданий токен валідний.
  2. Обмеження доступу: визначити, чи має користувач з наданим токеном право доступу до ресурсу.

У FastAPI для цих задач ми використовуємо:

  • Механізм Depends для інжекції залежностей у маршрути.
  • Перевірку JWT через PyJWT для верифікації підпису та витягнення даних.

Приклад обмеження доступу до ресурсів

Почнемо з простого прикладу. Уявімо, що в нас є захищений ендпоінт /protected, доступ до якого дозволений тільки автентифікованим користувачам.

Крок 1: створимо файл dependencies.py

Цей модуль буде містити функції, які ми підключатимемо через Depends.


from fastapi import HTTPException, Security, Depends
from fastapi.security import HTTPBearer
from jose import jwt, JWTError

SECRET_KEY = "supersecretkey123"  # Використовуйте безпечний ключ у реальному проєкті
ALGORITHM = "HS256"

# Визначаємо схему авторизації через HTTPBearer (заголовок Authorization)
oauth2_scheme = HTTPBearer()

def verify_jwt(token: str):
    try:
        # Перевіряємо й декодуємо JWT
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload  # Повертаємо дані токена (наприклад, user_id)
    except JWTError:
        raise HTTPException(status_code=401, detail="Невірний токен або термін дії токена минув")
    
def get_current_user(token: str = Depends(oauth2_scheme)):
    # Отримуємо токен із заголовків через HTTPBearer
    return verify_jwt(token)  # Декодуємо й перевіряємо токен

Крок 2: обмежимо доступ до ендпоінту за допомогою залежності

Створимо захищений ендпоінт /protected.


from fastapi import FastAPI, Depends
from .dependencies import get_current_user

app = FastAPI()

@app.get("/protected")
def protected_route(current_user: dict = Depends(get_current_user)):
    # current_user містить дані користувача з токена
    return {"message": f"Ласкаво просимо, {current_user.get('sub')}!"}

Що тут відбувається?

  1. В ендпоінті ми підключаємо залежність get_current_user через Depends.
  2. get_current_user перевіряє токен через verify_jwt, піднімає помилку, якщо токен недійсний, і повертає дані користувача (payload).
  3. У разі успіху користувач отримує доступ до ендпоінту.

Налаштування декораторів для захисту маршрутів

Часто буває так, що потрібно захистити одразу кілька ендпоінтів. Щоб не дублювати код, можна створити кастомний декоратор.

Ось приклад кастомного декоратора.


from fastapi import Request, HTTPException

def jwt_required(func):
    async def wrapper(request: Request, *args, **kwargs):
        token = request.headers.get("Authorization")
        if not token or not token.startswith("Bearer "):
            raise HTTPException(status_code=401, detail="Токен не надан")
        try:
            token = token.split(" ")[1]  # Витягуємо сам токен
            payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
            request.state.user = payload  # Зберігаємо дані токена в request
        except JWTError:
            raise HTTPException(status_code=401, detail="Невірний токен")
        return await func(request, *args, **kwargs)
    return wrapper

Тепер ми можемо використовувати декоратор так:


@app.get("/protected-decorator")
@jwt_required
async def protected_with_decorator(request: Request):
    user = request.state.user
    return {"message": f"Привіт, {user.get('sub')}!"}

Розділення доступу за ролями

У реальному світі не всі користувачі рівні. Дехто може бути адміністратором, інші — звичайними користувачами. Додамо перевірку ролей у нашу систему.

Крок 1: оновимо функцію перевірки токенів

Додамо логічну перевірку на наявність потрібної ролі.


def get_current_admin(token: str = Depends(oauth2_scheme)):
    payload = verify_jwt(token.credentials)
    if payload.get("role") != "admin":
        raise HTTPException(status_code=403, detail="Доступ заборонено: недостатньо прав")
    return payload

Крок 2: використання залежності для перевірки ролей


@app.get("/admin")
def admin_route(current_admin: dict = Depends(get_current_admin)):
    return {"message": f"Ласкаво просимо в адмін-зону, {current_admin.get('sub')}!"}

Тепер доступ до маршруту /admin є лише в користувачів з роллю admin.


Обробка помилок при перевірці токенів

Продовжимо з кращими практиками: важливо бути готовими до будь-яких несподіванок. Наприклад, користувач може надіслати:

  1. Прострочений токен.
  2. Токен з неправильною підписом.
  3. Токен з зміненими даними.

Усі ці випадки вже обробляються у функції verify_jwt через підняття виключень HTTPException. Однак, для кращого дебагу, ми можемо логувати такі події.


import logging

logger = logging.getLogger(__name__)

def verify_jwt_with_logging(token: str):
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload
    except JWTError as e:
        logger.error(f"Помилка перевірки токена: {e}")
        raise HTTPException(status_code=401, detail="Невірний токен")

Практична примітка

У реальних застосунках захист ендпоінтів з використанням JWT часто зустрічається в таких сценаріях:

  • Обмежений доступ до ресурсів, наприклад, персональних даних користувача.
  • Захист адміністративних маршрутів (наприклад, /admin, /dashboard).
  • Впровадження багаторівневої авторизації з розмежуванням доступу за ролями.

Тепер ви на крок ближче до створення захищених, надійних API. Приклади, які ми розглянули, легко масштабуются і можуть бути адаптовані під будь-які потреби.

Корисні посилання:

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