Сегодня мы научимся:
- Интегрировать 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
# Генерация токена доступа
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, который проверяет данные пользователя, генерирует токен доступа и возвращает его:
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. Если переданы правильные данные, вы получите токен доступа.
Защита эндпоинтов
Добавим защиту маршрутов с проверкой токенов. Для этого используем 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, чтобы обновлять токены доступа:
@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"}
Теперь пользователи могут безопасно обновлять свои токены доступа.
Безопасность и улучшения
- Храните секреты в переменных окружения. Ваш ключ
SECRET_KEYне должен быть в коде. - Устанавливайте короткий срок жизни токенов доступа. Например, 15-30 минут.
- Используйте HTTPS. Никогда не передавайте токены по незащищённым соединениям.
- Добавьте чёрный список для отозванных токенов. Это поможет заблокировать скомпрометированные токены.
Эта система аутентификации и авторизации отлично подходит для большинства API. Используйте её как базу и расширяйте под ваши нужды, добавляя, например, поддержку нескольких типов ролей, интеграцию с фронтендом или использование реальных баз данных. Это реализация реального рабочего кейса защиты API, так что теперь вы готовы к разработке API как профессионал! 🎉
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ