307 lines
14 KiB
Markdown
307 lines
14 KiB
Markdown
# Безопасность проекта — объяснение для чайников
|
||
|
||
Этот документ объясняет простым языком, **что и зачем** было сделано для защиты API.
|
||
|
||
---
|
||
|
||
## Оглавление
|
||
|
||
1. [Что такое «небезопасный» API и почему это плохо](#1-что-такое-небезопасный-api)
|
||
2. [Что было сделано — краткий список](#2-что-было-сделано)
|
||
3. [Аутентификация — кто ты такой?](#3-аутентификация)
|
||
4. [JWT-токены — цифровой пропуск](#4-jwt-токены)
|
||
5. [Хеширование паролей](#5-хеширование-паролей)
|
||
6. [Защита маршрутов (роутов)](#6-защита-маршрутов)
|
||
7. [Admin Key — ключ администратора](#7-admin-key)
|
||
8. [Защита документации `/docs`](#8-защита-документации-docs)
|
||
9. [CORS — кто может обращаться к API из браузера](#9-cors)
|
||
10. [Переменные окружения — секреты не в коде](#10-переменные-окружения)
|
||
11. [Как всё настроить — пошаговая инструкция](#11-как-настроить)
|
||
12. [Как использовать API — примеры](#12-примеры-использования)
|
||
|
||
---
|
||
|
||
## 1. Что такое «небезопасный» API
|
||
|
||
Представь, что у тебя есть склад. **Небезопасный API** — это когда дверь склада открыта для всех: любой может зайти, взять что хочет или сломать что хочет.
|
||
|
||
Конкретно, без защиты:
|
||
- любой человек в интернете мог удалить любого пользователя
|
||
- любой мог посмотреть чужие данные
|
||
- пароли хранились бы в открытом виде — украли базу = украли все пароли
|
||
- документация (Swagger) была видна всем, давая полную карту API для взломщика
|
||
|
||
---
|
||
|
||
## 2. Что было сделано
|
||
|
||
| Что | Зачем |
|
||
|-----|-------|
|
||
| JWT-токены | Пользователь должен войти, чтобы делать что-то важное |
|
||
| Хеширование паролей (bcrypt) | Даже если украдут БД — пароли не читаемы |
|
||
| Проверка владельца при PUT/DELETE | Ты можешь менять только свои данные |
|
||
| Admin Key | Опасные операции (инициализация БД) — только для администратора |
|
||
| HTTP Basic Auth на `/docs` | Документация API скрыта за паролем |
|
||
| CORS | Браузерные запросы принимаются только с разрешённых сайтов |
|
||
| Секреты в переменных окружения | Пароли и ключи не хранятся в коде |
|
||
|
||
---
|
||
|
||
## 3. Аутентификация
|
||
|
||
**Аутентификация** = подтверждение того, кто ты есть.
|
||
|
||
Как это работает в нашем API:
|
||
|
||
```
|
||
1. Ты отправляешь логин и пароль на POST /auth/login
|
||
2. Сервер проверяет: правильный ли пароль?
|
||
3. Если да — сервер выдаёт тебе "токен" (длинную строку-пропуск)
|
||
4. При каждом следующем запросе ты добавляешь этот токен в заголовок
|
||
5. Сервер смотрит на токен и понимает, кто ты
|
||
```
|
||
|
||
Аналогия: как билет в кино. Купил один раз — показываешь при входе, не нужно снова платить.
|
||
|
||
**Регистрация** — `POST /auth/register`:
|
||
```json
|
||
{
|
||
"username": "ivan",
|
||
"password": "мойпароль123",
|
||
"first_name": "Иван",
|
||
"last_name": "Иванов"
|
||
}
|
||
```
|
||
|
||
**Вход** — `POST /auth/login` (формат form-data):
|
||
```
|
||
username=ivan
|
||
password=мойпароль123
|
||
```
|
||
|
||
Ответ:
|
||
```json
|
||
{
|
||
"access_token": "eyJhbGci...(длинная строка)...",
|
||
"token_type": "bearer"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. JWT-токены
|
||
|
||
**JWT** (JSON Web Token) — это как паспорт, только цифровой.
|
||
|
||
Токен состоит из трёх частей, разделённых точкой:
|
||
|
||
```
|
||
eyJhbGciOiJIUzI1NiJ9 . eyJzdWIiOiIxMjMifQ . SflKxwRJSMeKKF2QT4
|
||
заголовок данные подпись
|
||
```
|
||
|
||
- **Заголовок**: тип токена и алгоритм шифрования
|
||
- **Данные (payload)**: ID пользователя, время истечения токена
|
||
- **Подпись**: криптографическая подпись — без секретного ключа сервера её нельзя подделать
|
||
|
||
Аналогия: токен — это как печать на паспорте. Печать нельзя нарисовать самому (без настоящей печати), поэтому её нельзя подделать.
|
||
|
||
**Как использовать токен в запросах:**
|
||
|
||
В заголовке HTTP:
|
||
```
|
||
Authorization: Bearer eyJhbGci...(твой токен)...
|
||
```
|
||
|
||
**Срок жизни токена** задаётся переменной `ACCESS_TOKEN_EXPIRE_MINUTES` (по умолчанию 60 минут).
|
||
|
||
---
|
||
|
||
## 5. Хеширование паролей
|
||
|
||
Пароли **никогда** не хранятся в открытом виде. Вместо этого хранится их хеш.
|
||
|
||
```
|
||
Пароль: "мойпароль123"
|
||
↓ bcrypt
|
||
Хеш: "$2b$12$eImiTXuWVxfM37uY4JANjQ..."
|
||
```
|
||
|
||
**Почему это безопасно:**
|
||
- Хеш нельзя "расшифровать" обратно в пароль
|
||
- Даже если база данных будет украдена — злоумышленник увидит только хеши
|
||
- При проверке он не расшифровывает, а снова хеширует введённый пароль и сравнивает хеши
|
||
|
||
Алгоритм **bcrypt** специально медленный — это делает перебор паролей (brute force) очень долгим.
|
||
|
||
---
|
||
|
||
## 6. Защита маршрутов
|
||
|
||
Некоторые действия требуют авторизации, другие открыты для всех.
|
||
|
||
| Маршрут | Открыт? | Объяснение |
|
||
|---------|---------|-----------|
|
||
| `POST /auth/register` | ✅ Да | Регистрация открыта всем |
|
||
| `POST /auth/login` | ✅ Да | Вход открыт всем |
|
||
| `GET /groups/` | ✅ Да | Просмотр групп — публичное |
|
||
| `POST /groups/` | 🔒 Нет | Создать группу — нужен токен |
|
||
| `PUT /users/{id}` | 🔒 Нет | Изменить профиль — только свой |
|
||
| `DELETE /users/{id}` | 🔒 Нет | Удалить — только свой аккаунт |
|
||
| `POST /init_database` | 🔒 Admin | Только с Admin Key |
|
||
|
||
**Проверка владельца**: при попытке изменить или удалить чужие данные сервер вернёт ошибку `403 Forbidden`.
|
||
|
||
---
|
||
|
||
## 7. Admin Key
|
||
|
||
Некоторые операции очень опасны (например, инициализация базы данных). Для них нужен специальный ключ администратора.
|
||
|
||
**Как использовать:**
|
||
```
|
||
Заголовок запроса: X-Admin-Key: твой_секретный_admin_ключ
|
||
```
|
||
|
||
Если `ADMIN_KEY` не задан в переменных окружения — такие операции полностью отключены (сервер вернёт `503`).
|
||
|
||
---
|
||
|
||
## 8. Защита документации `/docs`
|
||
|
||
Swagger UI (`/docs`) — это интерактивная документация API. Она показывает все маршруты, параметры, позволяет делать запросы прямо из браузера. Это очень удобно для разработчика, но и очень удобно для взломщика.
|
||
|
||
**Решение:** HTTP Basic Auth — браузер сам показывает окошко с запросом пароля.
|
||
|
||
```
|
||
Открываешь http://твой-сервер/docs
|
||
↓
|
||
Браузер показывает окошко: "Введите логин и пароль"
|
||
↓
|
||
Вводишь DOCS_USERNAME и DOCS_PASSWORD
|
||
↓
|
||
Видишь документацию
|
||
```
|
||
|
||
То же самое работает для `/redoc` и `/openapi.json`.
|
||
|
||
---
|
||
|
||
## 9. CORS
|
||
|
||
**CORS** (Cross-Origin Resource Sharing) — механизм браузера, который контролирует, с каких сайтов можно делать запросы к API.
|
||
|
||
Аналогия: CORS — это как охранник на входе, который проверяет твой бейдж (адрес сайта). Если бейдж не из списка — не пропустит.
|
||
|
||
Разрешённые источники в нашем проекте:
|
||
```
|
||
http://localhost:3000
|
||
http://127.0.0.1:3000
|
||
```
|
||
|
||
Это значит, что только фронтенд, запущенный локально на порту 3000, может обращаться к API из браузера.
|
||
|
||
> ⚠️ **Важно для production**: если у тебя есть публичный домен, добавь его в список `allow_origins` в `main.py`.
|
||
|
||
---
|
||
|
||
## 10. Переменные окружения
|
||
|
||
**Секреты никогда не должны быть в коде.** Если код попадёт на GitHub — все пароли станут публичными.
|
||
|
||
Все секреты хранятся в переменных окружения (в `docker-compose.yml` или в `.env` файле).
|
||
|
||
| Переменная | Что это | Как создать |
|
||
|-----------|---------|------------|
|
||
| `SECRET_KEY` | Ключ для подписи JWT-токенов | `python -c "import secrets; print(secrets.token_hex(32))"` |
|
||
| `ADMIN_KEY` | Пароль для admin-операций | Придумай длинную случайную строку |
|
||
| `DOCS_USERNAME` | Логин для доступа к `/docs` | Например: `admin` |
|
||
| `DOCS_PASSWORD` | Пароль для доступа к `/docs` | Придумай надёжный пароль |
|
||
| `ACCESS_TOKEN_EXPIRE_MINUTES` | Сколько минут живёт токен | По умолчанию 60 |
|
||
|
||
---
|
||
|
||
## 11. Как настроить
|
||
|
||
### Шаг 1. Сгенерируй SECRET_KEY
|
||
|
||
Открой терминал и запусти:
|
||
```bash
|
||
python -c "import secrets; print(secrets.token_hex(32))"
|
||
```
|
||
Скопируй результат — это твой `SECRET_KEY`.
|
||
|
||
Сделай то же самое для `ADMIN_KEY`.
|
||
|
||
### Шаг 2. Задай пароль для документации
|
||
|
||
Придумай любой логин и пароль для `DOCS_USERNAME` и `DOCS_PASSWORD`.
|
||
|
||
### Шаг 3. Обнови docker-compose.yml
|
||
|
||
Найди блок `environment` в `docker-compose.yml` и замени все `REPLACE_ME_...` на реальные значения:
|
||
|
||
```yaml
|
||
environment:
|
||
- SECRET_KEY=abcdef1234... # результат из шага 1
|
||
- ADMIN_KEY=supersecret456 # результат из шага 1
|
||
- DOCS_USERNAME=admin
|
||
- DOCS_PASSWORD=мойпарольдокументации
|
||
```
|
||
|
||
### Шаг 4. Запусти проект
|
||
|
||
```bash
|
||
docker-compose up --build
|
||
```
|
||
|
||
### Шаг 5. Проверь
|
||
|
||
Открой в браузере `http://localhost:8000/docs` — должно появиться окошко с запросом пароля.
|
||
|
||
---
|
||
|
||
## 12. Примеры использования
|
||
|
||
### Зарегистрироваться
|
||
```bash
|
||
curl -X POST http://localhost:8000/auth/register \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"username":"ivan","password":"пароль123","first_name":"Иван","last_name":"Иванов"}'
|
||
```
|
||
|
||
### Войти и получить токен
|
||
```bash
|
||
curl -X POST http://localhost:8000/auth/login \
|
||
-d "username=ivan&password=пароль123"
|
||
```
|
||
|
||
### Использовать токен в запросе
|
||
```bash
|
||
TOKEN="eyJhbGci..." # твой токен из предыдущего шага
|
||
|
||
curl -X GET http://localhost:8000/users/me \
|
||
-H "Authorization: Bearer $TOKEN"
|
||
```
|
||
|
||
### Создать группу (нужен токен)
|
||
```bash
|
||
curl -X POST http://localhost:8000/groups/ \
|
||
-H "Authorization: Bearer $TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"name_group":"Группа 1","user_id":"uuid-пользователя"}'
|
||
```
|
||
|
||
### Открыть документацию в браузере
|
||
Просто зайди на `http://localhost:8000/docs` — браузер спросит логин/пароль.
|
||
|
||
---
|
||
|
||
## Что ещё можно улучшить (на будущее)
|
||
|
||
- **Rate limiting** — ограничение количества запросов с одного IP (защита от брутфорса)
|
||
- **Refresh tokens** — долгоживущие токены для обновления access token без повторного входа
|
||
- **HTTPS** — шифрование соединения (критично в production, настраивается на уровне nginx/proxy)
|
||
- **Логирование** — запись подозрительных действий (много неудачных попыток входа)
|
||
- **Email-верификация** — подтверждение email при регистрации
|