Сьогодні ми навчимось:
- Інтегрувати JWT для повного захисту API.
- Реалізовувати аутентифікацію та авторизацію на основі ролей.
- Створювати систему управління правами доступу.
- Будувати безпечну архітектуру API з кращими практиками.
Перш ніж ми крокнемо в захопливий світ ролевої авторизації, давайте переконаємось, що в нас вже налаштований базовий захист API. Для цього створимо додаток, де користувачі можуть зареєструватись, увійти в систему, отримувати доступ до захищених маршрутів і оновлювати свої токени.
Підготовка додатку
Для початку створимо структуру FastAPI додатку і підключимо потрібні залежності:
pip install fastapi[all] pyjwt passlib
Налаштування моделей користувачів
Наша прикладна база даних буде зберігати інформацію про користувачів, їх паролі та ролі. Для простоти використаємо словник як емуляцію бази даних:
from passlib.context import CryptContext
# "Емуляція" бази даних
users_db = {
"johndoe": {
"username": "johndoe",
"password": "$2b$12$...",
"role": "admin"
},
"janedoe": {
"username": "janedoe",
"password": "$2b$12$...",
"role": "user"
}
}
# Налаштування хешування паролів
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_user(username: str):
return users_db.get(username)
Примітка: паролі в базі зберігаємо тільки в зашифрованому вигляді, щоб не піддавати користувачів ризику. Не повторюйте помилок юності!
Генерація та перевірка JWT
Створимо утиліти для роботи з токенами. Для цього використаємо бібліотеку PyJWT.
from datetime import datetime, timedelta
from jose import JWTError, jwt
SECRET_KEY = "supersecretkey" # Замініть на унікальний ключ (і тримайте в секреті!)
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# Генерація access 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})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Перевірка токена
def verify_token(token: str):
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except JWTError:
return None
Тепер у нас є базові функції для створення і перевірки токенів. Настав час впровадити їх у наш додаток.
Створення системи аутентифікації
Додамо ендпоінт /login, який перевіряє дані користувача, генерує access token і повертає його:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordRequestForm
app = FastAPI()
@app.post("/login")
def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = get_user(form_data.username)
if not user or not verify_password(form_data.password, user["password"]):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Невірний логін або пароль"
)
# Генеруємо токен з username і роллю
access_token = create_access_token(data={"sub": user["username"], "role": user["role"]})
return {"access_token": access_token, "token_type": "bearer"}
Тепер ви можете входити в систему, надсилаючи запити через curl або Postman. Якщо передані правильні дані, ви отримаєте access token.
Захист ендпоінтів
Додамо захист маршрутів з перевіркою токенів. Для цього використовуємо Depends і обробник токенів.
from fastapi.security import OAuth2PasswordBearer
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="login")
def get_current_user(token: str = Depends(oauth2_scheme)):
payload = verify_token(token)
if payload is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Невалідний токен"
)
return payload
Тепер можна захистити ендпоінт, додавши залежність:
@app.get("/users/me")
def read_current_user(current_user: dict = Depends(get_current_user)):
return {"username": current_user["sub"], "role": current_user["role"]}
Спробуйте запросити /users/me з токеном. Якщо токен валідний, ви отримаєте інформацію про користувача.
Додаємо авторизацію на основі ролей
А що як ваш друг Джон хоче обмежити доступ до важливих даних тільки для адміністраторів? Ми можемо легко реалізувати це за допомогою перевірки ролі в JWT.
Додамо декоратор для перевірки прав доступу на основі ролей:
def require_role(required_role: str):
def decorator(current_user: dict = Depends(get_current_user)):
if current_user["role"] != required_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Недостатньо прав"
)
return current_user
return Depends(decorator)
Тепер ми можемо захистити маршрути, використовуючи цей декоратор:
@app.get("/admin/data")
def read_admin_data(current_user: dict = require_role("admin")):
return {"message": "Доступ дозволено лише адміністраторам"}
Якщо користувач з роллю user спробує отримати доступ до /admin/data, він отримає помилку 403 Forbidden.
Система оновлення токенів
Додамо ендпоінт /refresh, щоб оновлювати access token:
@app.post("/refresh")
def refresh_token(token: str = Depends(oauth2_scheme)):
payload = verify_token(token)
if payload is None or payload.get("role") is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Невалідний токен"
)
# Генеруємо новий токен
new_token = create_access_token(data={"sub": payload["sub"], "role": payload["role"]})
return {"access_token": new_token, "token_type": "bearer"}
Тепер користувачі можуть безпечно оновлювати свої access token.
Безпека та покращення
- Зберігайте секрети у змінних оточення. Ваш ключ
SECRET_KEYне повинен бути в коді. - Встановлюйте короткий термін життя токенів доступу. Наприклад, 15–30 хвилин.
- Використовуйте HTTPS. Ніколи не передавайте токени по незахищених з'єднаннях.
- Додайте чорний список для відкликаних токенів. Це допоможе заблокувати скомпрометовані токени.
Ця система аутентифікації та авторизації чудово підходить для більшості API. Використовуйте її як базу і розширюйте під ваші потреби, додаючи, наприклад, підтримку кількох типів ролей, інтеграцію з фронтендом або використання реальних баз даних. Це реалізація реального робочого кейсу захисту API, тож тепер ви готові до розробки API як професіонал! 🎉
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ