Представим, что вы разрабатываете SaaS-приложение на React, FastAPI и PostgreSQL. В системе есть пользователи, организации, проекты, документы и платежи. Frontend отправляет запросы: GET /api/projects POST /api/projects GET /api/documents/42 DELETE /api/documents/42 Все Endpoint работают корректно. Авторизация реализована через JWT. HTTPS настроен. CORS разрешает запросы с домена приложения. Кажется, что API защищён.
Но представим ситуацию. Пользователь открывает документ: GET /api/documents/42 Затем меняет идентификатор: GET /api/documents/43 И неожиданно получает документ другой организации. JWT действителен. HTTPS работает. CORS настроен правильно. Но приложение всё равно допускает утечку данных. Причина — отсутствие проверки прав доступа к конкретному объекту. Это один из наиболее опасных классов уязвимостей современных API.
В этой статье разберём безопасность REST API, OWASP API Security Top 10, Broken Object Level Authorization, IDOR уязвимость, JWT Security, CORS FastAPI, Rate Limiting FastAPI, защиту от SQL Injection, безопасную валидацию запросов и архитектуру защищённого API для Production.
1. Почему безопасность API важнее, чем кажется
Современный Backend редко работает изолированно. К нему обращаются:
- браузерные приложения;
- мобильные клиенты;
- внешние интеграции;
- фоновые сервисы;
- административные панели;
- партнёрские системы.
Каждый Endpoint потенциально становится точкой атаки. Например: Internet | v Reverse Proxy | v FastAPI | +--> PostgreSQL | +--> Redis | +--> Object Storage | +--> External APIs Ошибка в одном Endpoint может позволить злоумышленнику получить данные из нескольких внутренних систем. Поэтому защита Backend API должна строиться на нескольких независимых уровнях.
2. Что такое Defense in Depth
Defense in Depth API Security — подход, при котором безопасность обеспечивается несколькими механизмами одновременно. Например: Client | v HTTPS | v Reverse Proxy | v Rate Limiting | v Authentication | v Authorization | v Input Validation | v Business Logic | v Database Permissions Если один механизм окажется недостаточным, другие могут уменьшить последствия атаки. Но важно понимать: эти уровни не взаимозаменяемы.
Например, Rate Limiting не заменяет авторизацию. А HTTPS не защищает от ошибок бизнес-логики.
3. Какие угрозы существуют для REST API
Основные угрозы REST API: Безопасность API требует учитывать как технические, так и бизнес-риски.
4. Что такое OWASP API Security Top 10
OWASP API Security Top 10 — проект OWASP, описывающий важные категории рисков безопасности API. Версия OWASP API Security Top 10 2023 включает: Этот список полезно использовать как основу для Security Review. Однако он не охватывает абсолютно все возможные уязвимости.
5. Что такое Broken Access Control
Broken Access Control REST API — ситуация, когда приложение неправильно ограничивает действия пользователя. Например: DELETE /api/projects/100 Пользователь авторизован. Но проект принадлежит другой организации. Если Backend проверяет только наличие JWT, удаление может оказаться доступным. Правильная логика должна проверять не только личность пользователя, но и его полномочия.
6. Что такое BOLA
BOLA Broken Object Level Authorization — отсутствие или неправильная реализация проверки доступа к конкретному объекту. Например: GET /api/invoices/501 Backend должен убедиться, что пользователь имеет право просматривать Invoice 501. Наличие действительного токена само по себе этого не доказывает. BOLA особенно опасна в Multi-Tenant SaaS, где одна база данных содержит данные множества организаций.
7. Что такое IDOR
IDOR уязвимость — Insecure Direct Object Reference. Это ситуация, когда пользователь может манипулировать идентификатором объекта и получать несанкционированный доступ. Например: GET /api/users/123/documents Злоумышленник меняет 123 на 124. Если сервер не проверяет права, он может раскрыть чужие документы. IDOR часто является проявлением BOLA. Но важно не сводить проблему только к предсказуемым идентификаторам.
8. Почему UUID не защищает от IDOR
UUID не заменяет авторизацию. Предположим, объект имеет идентификатор: 550e8400-e29b-41d4-a716-446655440000 Такой ID сложнее угадать, чем последовательное число. Но он может оказаться в:
- URL;
- логах;
- письмах;
- истории браузера;
- ошибках;
- сторонних интеграциях.
Если пользователь получил UUID чужого объекта, он всё равно не должен иметь доступ. Безопасность обеспечивается проверкой полномочий, а не секретностью идентификатора.
9. Как защититься от BOLA в FastAPI
FastAPI Object Level Authorization должна проверять принадлежность объекта текущему пользователю или организации. Плохой пример: @app.get("/documents/{document_id}") async def get_document(document_id: int): return await repository.get(document_id) Здесь отсутствует проверка прав. Более безопасный подход: @app.get("/documents/{document_id}") async def get_document( document_id: int,
current_user: User = Depends(get_current_user), ): document = await repository.get_for_user( document_id=document_id, user_id=current_user.id, ) if document is None: raise HTTPException( status_code=404, detail="Document not found", ) return document Ключевая идея — получать только объекты, доступные текущему пользователю.
Функция get_for_user должна действительно применять ограничения на уровне запроса или проверенной политики доступа.
10. Как ограничить доступ на уровне SQL
PostgreSQL Tenant Isolation можно реализовать через условия запроса. Например: SELECT id, title, content FROM documents WHERE id = :document_id AND organization_id = :organization_id; Backend получает organization_id из проверенного контекста авторизации. Нельзя доверять произвольному organization_id, переданному клиентом, без проверки членства пользователя в организации.
Для сложных Multi-Tenant систем можно дополнительно рассмотреть PostgreSQL Row-Level Security.
11. Что такое Row-Level Security
PostgreSQL Row Level Security — механизм ограничения доступа к строкам таблицы на уровне базы данных. Например, политика может разрешать читать только документы определённой организации. Но RLS требует аккуратной настройки:
- ролей базы данных;
- контекста Tenant;
- политик;
- соединений из Pool;
- поведения транзакций.
Особенно важно не допускать утечки Tenant Context между запросами при повторном использовании соединений. RLS полезен как дополнительный уровень защиты, но не заменяет проектирование бизнес-авторизации.
12. Что такое Broken Function Level Authorization
Broken Function Level Authorization возникает, когда пользователь получает доступ к функции, которую не должен вызывать. Например: POST /api/admin/users/42/ban Если Backend проверяет только наличие JWT, обычный пользователь может попытаться вызвать административную операцию. Правильная реализация должна проверять необходимые полномочия. Например: def require_admin( current_user: User = Depends(get_current_user), ):
if not current_user.is_admin: raise HTTPException(status_code=403) return current_user Однако глобальная роль admin не всегда достаточна. Администратор одной организации не обязательно имеет права в другой.
13. Что такое RBAC
RBAC Role Based Access Control — управление доступом на основе ролей. Например: RBAC удобен для типовых бизнес-сценариев. Но роль должна оцениваться в правильном контексте. Например: User 42 | +--> Organization A: Admin | +--> Organization B: Viewer Проверка только глобальной роли пользователя приведёт к ошибкам.
14. Что такое ABAC
ABAC Attribute Based Access Control — управление доступом на основе атрибутов. Например, разрешение зависит от:
- роли пользователя;
- организации;
- владельца документа;
- статуса проекта;
- типа операции;
- дополнительных бизнес-условий.
Условно: def can_edit_document(user, document): return ( user.organization_id == document.organization_id and user.role in {"editor", "admin"} and document.status != "archived" ) Это упрощённый пример. В реальном Multi-Tenant приложении пользователь может состоять в нескольких организациях, поэтому членство и роль лучше получать из проверенной записи Membership.
15. Что такое Broken Authentication
Broken Authentication API — ошибки в механизмах подтверждения личности пользователя. Например:
- слабая проверка паролей;
- неправильная обработка токенов;
- отсутствие защиты от перебора;
- ошибки восстановления доступа;
- небезопасные Refresh Tokens;
- неправильная проверка JWT.
Даже идеальная авторизация не поможет, если злоумышленник сможет легко захватить аккаунт.
16. Как безопасно хранить пароли
Безопасное хранение паролей Backend предполагает использование специализированных алгоритмов Password Hashing. Например:
- Argon2id;
- scrypt;
- bcrypt при корректной настройке.
Нельзя хранить пароли в открытом виде. Также не следует использовать обычный SHA-256 без специализированной схемы Password Hashing. Для новых систем Argon2id часто является предпочтительным выбором при подходящей поддержке библиотек и инфраструктуры.
17. Как защитить Login Endpoint
Защита Login от Brute Force может включать:
- Rate Limiting;
- задержки после неудачных попыток;
- мониторинг аномалий;
- MFA;
- защиту восстановления пароля;
- обнаружение Credential Stuffing.
При этом нельзя полагаться только на лимит по IP. Злоумышленник может использовать множество IP-адресов. А большое количество обычных пользователей может находиться за одним NAT.
18. Что такое JWT
JWT JSON Web Token безопасность связана с правильной проверкой подписанных Claims. JWT обычно состоит из трёх частей: header.payload.signature Подпись позволяет проверить целостность токена и его происхождение при использовании доверенного ключа. Но обычный подписанный JWT не является зашифрованным. Его Payload можно декодировать. Поэтому в JWT не следует помещать секретные данные.
19. Какие Claims необходимо проверять
JWT Claims Validation может включать: Не все Claims обязательны для любого JWT по стандарту. Но приложение должно явно определять, какие Claims необходимы для конкретного типа токена. Особенно важны проверка подписи, алгоритма, срока действия, Issuer и Audience там, где они используются.
20. Как проверять JWT в FastAPI
FastAPI JWT Validation пример с PyJWT:
import os
import jwt
from fastapi import ( Depends, HTTPException, status, ) from fastapi.security import HTTPBearer security = HTTPBearer() JWT_SECRET = os.environ["JWT_SECRET"] JWT_ISSUER = "https://auth.example.com" JWT_AUDIENCE = "my-api" def get_current_user_id( credentials=Depends(security), ) -> str: try: payload = jwt.decode( credentials.credentials, JWT_SECRET, algorithms=["HS256"], issuer=JWT_ISSUER, audience=JWT_AUDIENCE,
options={ "require": [ "exp", "iss", "aud", "sub", ] }, ) return payload["sub"] except jwt.PyJWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", ) Это иллюстрация проверки токена. В Production необходимо также учитывать:
- тип токена;
- состояние пользователя;
- отзыв доступа;
- управление ключами;
- ротацию секретов;
- соответствие токена текущей сессии и политике приложения.
21. Почему нельзя доверять JWT Algorithm из заголовка
JWT Algorithm Confusion — класс ошибок, связанных с неправильным выбором алгоритма проверки подписи. Приложение не должно принимать произвольный алгоритм, указанный клиентом. Например, нельзя автоматически переключаться между HS256 и RS256 на основе недоверенного заголовка без строго определённой политики. Алгоритмы должны быть явно разрешены сервером.