Давайте представим ваш API как дверь в ваш дом. Если дверь всегда открыта и любой может войти, то это довольно-таки рискованная ситуация. В контексте API это грозит тем, что злоумышленники смогут получить доступ к данным вашего приложения или вызвать нежелательные изменения.
Токены безопасности — это ключи, которые предоставляются пользователям (или системам) для того, чтобы предоставить доступ к данным и сервисам API. Защитить API с использованием токенов — значит запереть дверь и выдавать ключи только тем, кто действительно имеет право войти.
Задачи токенов безопасности:
- Убедиться, что запрос исходит от авторизованного пользователя.
- Определить, какие действия пользователь может выполнять.
- Защитить ваши эндпоинты от несанкционированного доступа.
Введение в токены безопасности: JWT
Самым популярным вариантом для работы с токенами в современных API является JSON Web Token (JWT). Это стандарт RFC 7519, который позволяет безопасно передавать информацию между сторонами в виде JSON-объекта.
JWT — это не просто строка, а структурированный объект, который состоит из трех частей:
- Header (заголовок) — содержит информацию о типе токена и алгоритме шифрования.
- Payload (полезная нагрузка) — содержит данные, которые мы хотим передать (например, идентификатор пользователя).
- Signature (подпись) — используется для проверки того, что токен не был подделан.
Когда объединены эти три части, они образуют строку, которая выглядит примерно вот так:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjM0NTY3ODkwLCJleHAiOjE2ODg4OTk5OTl9.KyJw2aKXgWU1Lp5Jd0xWJoN3sUYOZ0qGqH8GDF8JMoQ
Установка необходимых библиотек
Для работы с токенами безопасности в FastAPI потребуется библиотека python-jose для генерации и проверки JWT. Также понадобится python-multipart для чтения данных формы (используется при аутентификации).
Установим их командой:
pip install python-jose python-multipart
Настройка безопасности в FastAPI
- Создание модели пользователя
Мы начнем с модели данных для пользователя. Эту модель мы будем использовать для хранения информации о пользователях, прошедших аутентификацию.
from pydantic import BaseModel class User(BaseModel): username: str full_name: str | None = None email: str | None = None disabled: bool | None = NoneТеперь создадим ещё одну модель для передачи данных при входе пользователя:
class UserInDB(User): hashed_password: strОбратите внимание на поле
hashed_password. Мы не храним пароли в открытом виде. Хранение "хэшированных" паролей — это безопасная практика. - Настройка базы данных пользователей
Для упрощения мы пока создадим базу данных пользователей в памяти:
db_users = { "jsmith": { "username": "jsmith", "full_name": "John Smith", "email": "jsmith@example.com", "hashed_password": "fakehashedpassword", "disabled": False, } } def get_user(db, username: str): user = db.get(username) if user: return UserInDB(**user) - Создание функций для работы с токенами
Для создания JWT используем библиотеку
jose.from jose import JWTError, jwt from datetime import datetime, timedelta SECRET_KEY = "secret" # Используйте более надёжный ключ в реальных проектах 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Для проверки токенов декодируем JWT и извлекаем данные.
def verify_token(token: str): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise ValueError("Invalid token") return username except JWTError: raise ValueError("Invalid token") - Реализация аутентификации
Теперь создадим эндпоинт, на котором пользователь сможет аутентифицироваться и получить токен.
from fastapi import Depends, FastAPI, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm app = FastAPI() oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") def authenticate_user(username: str, password: str): user = get_user(db_users, username) if not user or user.hashed_password != "fakehashedpassword": # Используйте bcrypt в реальных проектах return False return user @app.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): user = authenticate_user(form_data.username, form_data.password) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Incorrect username or password", headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": user.username}) return {"access_token": access_token, "token_type": "bearer"} - Защита эндпоинтов с использованием токенов
Теперь мы можем добавить защиту на эндпоинт, чтобы требовалось отправлять токен для доступа.
from fastapi import Depends async def get_current_user(token: str = Depends(oauth2_scheme)): try: username = verify_token(token) user = get_user(db_users, username) if user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) return user except ValueError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", headers={"WWW-Authenticate": "Bearer"}, ) @app.get("/users/me") async def read_users_me(current_user: User = Depends(get_current_user)): return current_user
Полезные советы и практическое применение
Токены безопасности используются повсеместно. Например:
- В e-commerce платформах для защиты личного кабинета.
- В корпоративных приложениях для разграничения прав доступа.
- В микросервисной архитектуре для авторизации между сервисами.
Учтите, что:
- Никогда не используйте простой текстовый пароль. Используйте библиотеки вроде
bcryptилиargon2для хэширования. - Токены имеют срок действия. Обновляйте их с помощью рефреш-токенов.
- Не передавайте токены через незащищённые соединения (используйте HTTPS).
Теперь у нас есть базовая настройка безопасности FastAPI с токенами. Мы сделали первый шаг к тому, чтобы не только создавать, но и защищать наши API.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ