Глава 6.3: Базовая аутентификация
Время изучения: 1 час
1. Аутентификация API: Пропуск в ЦУП
Аутентификация — это процесс проверки личности пользователя. В отличие от сайтов с сессиями и куки, stateless (не хранящие состояние) API обычно используют токены.
Процесс выглядит так:
- Пользователь отправляет свои логин и пароль на специальный эндпоинт (например,
/login
). - Сервер проверяет их. Если все верно, он генерирует уникальный, зашифрованный токен (длинную строку) и отправляет его обратно.
- При каждом последующем запросе к защищенным ресурсам (например,
POST /planets
) пользователь должен прикреплять этот токен в заголовкеAuthorization
. - Сервер проверяет токен на валидность и, если он корректен, выполняет запрос.
💡 Космическая аналогия:
- Логин/пароль = Ваш биометрический скан для получения пропуска.
- Токен = Электронный пропуск (ID-карта), который вы получаете на входе в ЦУП.
- Заголовок
Authorization: Bearer <токен>
= Вы прикладываете свой пропуск к считывателю у каждой защищенной двери.- Защищенные эндпоинты (POST, PUT, DELETE) = Двери в серверную или к пульту управления запуском.
2. Аутентификация в Laravel: Sanctum
Laravel предлагает элегантное решение для аутентификации API — Laravel Sanctum. Он идеально подходит для SPA (одностраничных приложений), мобильных приложений и простых токен-based API.
Шаг 1: Установка и настройка Sanctum
Sanctum уже установлен в стандартном приложении Laravel, но проверим конфигурацию.
- Публикация конфигурации (если еще не сделали):
- Запуск миграций (создаст таблицу
personal_access_tokens
): - Добавление трейта в модель
User
: Откройтеapp/Models/User.php
и убедитесь, что он использует трейтHasApiTokens
.
Шаг 2: Создание эндпоинта для выдачи токенов Нам нужен роут, куда пользователь будет отправлять логин/пароль.
Добавьте в routes/api.php
:
// routes/api.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\Models\User;
use Illuminate\Validation\ValidationException;
Route::post('/login', function (Request $request) {
$request->validate([
'email' => 'required|email',
'password' => 'required',
]);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages([
'email' => ['Учетные данные неверны.'],
]);
}
// Возвращаем токен
return response()->json([
'token' => $user->createToken('api-token')->plainTextToken
]);
});
Для теста можно создать пользователя через сидер или Tinker.
Шаг 3: Защита роутов
Теперь защитим наши CRUD-операции. Изменим routes/api.php
:
// routes/api.php
use App\Http\Controllers\PlanetController;
// Публичный роут для просмотра планет
Route::get('/planets', [PlanetController::class, 'index']);
Route::get('/planets/{planet}', [PlanetController::class, 'show']);
// Группа защищенных роутов
Route::middleware('auth:sanctum')->group(function () {
Route::post('/planets', [PlanetController::class, 'store']);
Route::put('/planets/{planet}', [PlanetController::class, 'update']);
Route::delete('/planets/{planet}', [PlanetController::class, 'destroy']);
// Роут для выхода (удаления токена)
Route::post('/logout', function (Request $request) {
$request->user()->currentAccessToken()->delete();
return response()->json(['message' => 'Вы успешно вышли'], 200);
});
});
Middleware auth:sanctum
будет проверять наличие валидного токена в заголовке Authorization
.
3. Аутентификация в FastAPI: OAuth2 и JWT
В FastAPI нет встроенной системы аутентификации, но есть мощные инструменты для ее реализации. Стандарт де-факто — OAuth2 с JWT-токенами.
Шаг 1: Установка зависимостей
python-jose
: для создания и проверки JWT-токенов.passlib
: для хэширования и проверки паролей.python-multipart
: для обработки данных из форм (username
иpassword
).
Шаг 2: Создание модуля безопасности (security.py
)
Это хорошая практика — вынести всю логику аутентификации в отдельный файл.
Создайте файл security.py
:
# security.py
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta, timezone
# --- Настройки ---
SECRET_KEY = "your-super-secret-key-that-is-long-and-random" # ⚠️ Замените на свой ключ!
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
# --- Утилиты ---
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/login")
# --- Функции ---
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def create_access_token(data: dict):
to_encode = data.copy()
expire = datetime.now(timezone.utc) + 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 get_current_user(token: str = Depends(oauth2_scheme)):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Не удалось проверить учетные данные",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
except JWTError:
raise credentials_exception
# Здесь можно вернуть пользователя из БД, пока просто вернем имя
return {"username": username}
Шаг 3: Интеграция в main.py
Теперь подключим это к нашему приложению.
-
Создадим эндпоинт
/login
:# main.py from fastapi.security import OAuth2PasswordRequestForm from fastapi import Depends, APIRouter from . import security # Импортируем наш модуль # ... ваш код FastAPI ... router = APIRouter(prefix="/api/v1") @router.post("/login") def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()): # Здесь должна быть проверка пользователя в БД # Для примера, у нас есть один тестовый юзер is_user_valid = (form_data.username == "testuser" and security.verify_password("testpass", security.get_password_hash("testpass"))) if not is_user_valid: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Неверное имя пользователя или пароль", ) access_token = security.create_access_token(data={"sub": form_data.username}) return {"access_token": access_token, "token_type": "bearer"} # ... app.include_router(router)
-
Защитим эндпоинты:
Используем нашу зависимость
get_current_user
.# main.py или в вашем роутере планет @router.post("/planets", status_code=status.HTTP_201_CREATED) def create_planet( planet: PlanetCreate, current_user: dict = Depends(security.get_current_user) # <-- Защита! ): # Логика создания планеты... print(f"Пользователь {current_user['username']} создает планету.") # ... return new_planet # Так же защищаем PUT и DELETE
4. Использование токенов на фронтенде
Наш фронтенд теперь должен сначала получить токен, сохранить его (например, в localStorage
) и прикреплять к каждому защищенному запросу.
Пример на JavaScript (fetch
):
// 1. Логинимся
async function login(email, password) {
const response = await fetch('http://localhost:8001/api/login', { // Адрес Laravel API
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({email, password})
});
const data = await response.json();
if (data.token) {
localStorage.setItem('api_token', data.token); // Сохраняем токен
}
}
// 2. Делаем защищенный запрос
async function createPlanet(planetData) {
const token = localStorage.getItem('api_token');
const response = await fetch('http://localhost:8001/api/planets', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` // <--- Прикрепляем токен!
},
body: JSON.stringify(planetData)
});
// ...
}
Квиз для закрепления
🚀 Итог главы:
Вы установили "систему контроля доступа" на свои API. Теперь не каждый желающий может вносить изменения в вашу "галактическую базу данных".
- ✅ Поняли принцип токен-based аутентификации.
- 🔐 Реализовали выдачу токенов и защиту роутов в Laravel Sanctum.
- ⚙️ Настроили аутентификацию на основе OAuth2 и JWT в FastAPI.
- 🛰️ Узнали, как фронтенд должен сохранять и использовать токен.
Ваши API стали не только функциональными, но и безопасными. Однако, чтобы другие разработчики могли ими пользоваться, им нужны "инструкции по эксплуатации".
📌 Проверка:
- Попробуйте сделать
POST
-запрос на/api/planets
(в Laravel) или/api/v1/planets
(в FastAPI) без токена с помощью Postman или Insomnia. Вы должны получить ошибку401 Unauthorized
.- Сделайте запрос на
/login
, получите токен, добавьте его в заголовокAuthorization
и повторитеPOST
-запрос. Он должен выполниться успешно.