Ось твій урок у стилі CS50. Приготуйся, зараз будемо розбирати магію безпеки! 🚀
🛡️ Урок: OAuth2 та Безпека у FastAPI
(або "Як пустити в клуб тільки своїх і не перевіряти паспорт щоразу")
1. 🔥 Вступ: Проблема та Мотивація
Привіт, друзі! 👋
Уявіть, що ви заселилися у готель "All Inclusive". Ви підходите до бару за коктейлем. Бармен питає: "Ти хто?". Ви дістаєте паспорт, договір про бронювання, чек про оплату, показуєте це все. Він перевіряє 5 хвилин, наливає сік.
Через 10 хвилин ви хочете піти в басейн. Охоронець: "Ти хто?". Ви знову дістаєте стос паперів...
Звучить як пекло, правда?
Але ж у реальному житті це працює не так. Вам на руку чіпляють браслет. Цей браслет — це відповідь на питання "Чи можна цій людині бути тут?", без необхідності носити з собою сейф із документами.
У світі веб-розробки:
* Паспорт і документи — це ваш логін і пароль. Пересилати їх з кожним запитом (GET /me, POST /orders) — це небезпечно і незручно.
* Браслет — це Token (Токен).
Сьогодні ми навчимося створювати ці "браслети" за допомогою стандарту OAuth2 у FastAPI.
Без цього уроку ваш API — це прохідний двір, де будь-хто може видалити вашу базу даних. Готові закрити двері на замок? Поїхали!
2. 🧠 Теоретична база (без нудних лекцій)
Перш ніж писати код, розберемося з механікою.
🔑 Автентифікація vs Авторизація
Це плутають навіть мідли. Запам’ятайте раз і назавжди: 1. Автентифікація (Authentication): "Хто ти?" (Перевірка логіна/пароля). 2. Авторизація (Authorization): "Що тобі можна?" (Чи є в тебе права адміна?).
Сьогодні ми фокусуємось на першому, яке відкриває двері до другого.
📜 Що таке OAuth2?
Це не бібліотека, це протокол. Це набір правил, як домовлятися про доступ. У контексті FastAPI ми найчастіше використовуємо схему "Password Flow":
1. Клієнт відправляє username + password.
2. Сервер каже: "Ок, вірю" і віддає Token.
3. Клієнт у всіх наступних запитах показує тільки Token.
📦 Що таке JWT (JSON Web Token)?
Це формат нашого "браслета". Це не просто набір випадкових символів. Це зашифрований JSON, який містить інформацію про користувача.
Уявіть прозорий контейнер, у якому лежить записка: "Це Іван, він адмін, токен дійсний до 12:00". * Під капотом: Цей контейнер підписаний цифровим підписом (секретним ключем сервера). * Чому це круто: Якщо хакер спробує змінити "Іван" на "Хакер", підпис зламається, і сервер не прийме токен.
❗️ Що треба запам'ятати: Ми ніколи не зберігаємо паролі у відкритому вигляді. Ми хешуємо їх (перетворюємо на "фарш", з якого не відновити "м'ясо"). Але про хешування — трохи згодом, зараз фокус на токенах.
3. 🧪 Приклади: Від простого до магії
Нам знадобляться бібліотеки. Уявіть, що це наші інструменти для виготовлення перепусток.
pip install fastapi uvicorn python-multipart python-jose[cryptography] passlib[bcrypt]
(Не лякайтесь назв: multipart — для форм, jose — для роботи з JWT, passlib — для хешування паролів).
Крок 1: Найпростіша перевірка (Dummy Example)
Давайте зробимо "фейкову" базу даних і просто навчимо FastAPI розуміти, що ми хочемо використовувати OAuth2.
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
app = FastAPI()
# 1. Схема безпеки. Ми кажемо Swagger UI: "Ей, логін відбувається за адресою /token"
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
@app.get("/items/")
async def read_items(token: str = Depends(oauth2_scheme)):
# Тут магія: FastAPI автоматично шукає заголовок "Authorization: Bearer ..."
# Якщо токена немає — він сам викине помилку 401.
return {"token": token, "data": "Дуже секретні дані"}
🤔 Питання до вас: Якщо ви зараз запустите цей код і спробуєте зробити запит на /items/ без заголовків, що станеться?
(Відповідь: Ви отримаєте "Not authenticated". FastAPI зробив це за нас!)
Крок 2: Реальна генерація токена (The Real Deal)
Тепер зробимо ендпоінт, який дійсно перевіряє пароль і видає токен.
# Додамо трохи імітації бази даних
fake_users_db = {
"david": {"username": "david", "password": "secretpassword"},
"alice": {"username": "alice", "password": "wonderland"},
}
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
# OAuth2PasswordRequestForm — це спеціальний клас, який очікує поля 'username' та 'password'
user = fake_users_db.get(form_data.username)
# Перевірка: чи існує юзер і чи правильний пароль
if not user or user["password"] != form_data.password:
raise HTTPException(status_code=400, detail="Невірний логін або пароль")
# Якщо все ок — видаємо токен (поки що просто рядок, щоб зрозуміти суть)
return {"access_token": user["username"], "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(token: str = Depends(oauth2_scheme)):
# Тепер ми розшифровуємо токен (у нашому простому випадку — це просто ім'я)
user = fake_users_db.get(token)
if not user:
raise HTTPException(status_code=401, detail="Токен недійсний")
return user
Що ми зробили?
1. Створили /token, куди користувач шле логін/пароль.
2. Якщо вони вірні — віддаємо JSON з токеном.
3. Створили /users/me, який захищений. Він бере токен, дивиться на нього і розуміє, хто прийшов.
Крок 3: Справжній JWT (Рівень PRO) 🛡️
Просто віддавати ім'я як токен — це як написати "Перепустка" на серветці. Легко підробити. Використаємо криптографію.
from datetime import datetime, timedelta
from jose import JWTError, jwt
# СЕКРЕТНИЙ КЛЮЧ (У реальному житті це має бути в .env файлі!)
SECRET_KEY = "my_super_secret_key_shhh"
ALGORITHM = "HS256"
def create_access_token(data: dict):
to_encode = data.copy()
# Токен живе 30 хвилин
expire = datetime.utcnow() + timedelta(minutes=30)
to_encode.update({"exp": expire})
# Створюємо підписаний JWT
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
# Оновлений логін
@app.post("/token")
async def login_jwt(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or user["password"] != form_data.password:
raise HTTPException(status_code=400, detail="Error")
# ГЕНЕРУЄМО JWT
access_token = create_access_token(data={"sub": user["username"]})
return {"access_token": access_token, "token_type": "bearer"}
# Оновлена перевірка (Dependency)
async def get_current_user(token: str = Depends(oauth2_scheme)):
try:
# Спроба розшифрувати
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise HTTPException(status_code=401, detail="Invalid token")
except JWTError:
raise HTTPException(status_code=401, detail="Could not validate credentials")
user = fake_users_db.get(username)
return user
@app.get("/users/me")
async def read_users_me(current_user: dict = Depends(get_current_user)):
return current_user
Як це працює?
Коли клієнт присилає токен, ми намагаємось відкрити його нашим SECRET_KEY.
* Якщо токен підробив хакер — ключ не підійде ❌.
* Якщо час вийшов — бібліотека викине помилку ❌.
* Якщо все ок — ми дістаємо ім'я юзера ✅.
4. 🛠 Практична частина
Час закатати рукави! Ваше завдання не просто скопіювати, а відчути код.
- 🔹 Завдання "Hello World": Скопіюйте код з Кроку 3. Запустіть сервер (
uvicorn main:app --reload). Відкрийте Swagger UI (/docs). Натисніть зелену кнопку Authorize, введітьdavid/secretpassword. Перевірте, чи пускає вас на/users/me. - 🔹 Завдання "Новий гість": Додайте в
fake_users_dbсебе (своє ім'я і пароль). Спробуйте залогінитися. - 🔹 Завдання "Фейс-контроль": Змініть час життя токена на 10 секунд. Залогіньтесь, швидко зробіть запит (успіх), почекайте 11 секунд і зробіть запит знову. Що ви побачили?
- 🔹 Завдання "Злам системи": Отримайте токен. Скопіюйте його. Підіть на сайт jwt.io, вставте токен. Змініть у ньому "sub": "david" на "sub": "alice". Спробуйте використати цей підроблений токен у Swagger. (Спойлер: має бути помилка, бо підпис не зійдеться).
- 🔹 Міні-кейс: Додайте до ендпоінту
/users/meповернення повідомлення: "Привіт, [username], ласкаво просимо!".
5. 💡 Мислення як у розробника
Як думає новачок?
"Я зроблю так, щоб воно просто працювало. Зберігати паролі в Excel? Та хто там побачить!" 😨
Як думає Senior Engineer?
"Безпека — це шари. Ніколи не довіряй вхідним даним. Ніколи не пиши свою криптографію." 🧐
Типові помилки:
1. Зберігання SECRET_KEY в коді. Якщо ви запушите це на GitHub, вважайте, що вас зламали. Використовуйте змінні середовища (.env).
2. Зберігання паролів текстом. У нашому прикладі ми так робили для простоти. У реальності — тільки хеш (використовуйте bcrypt).
3. Винахід велосипеда. Не пишіть свою функцію генерації токенів. Бібліотеки писали математики, використовуйте їх.
6. 🧩 Підсумок
Сьогодні ми пройшли шлях від "хто ти такий?" до сучасної системи безпеки на основі JWT.
Що ви тепер вмієте: * ✅ Розуміти різницю між AuthN та AuthZ. * ✅ Створювати механізм входу (Login) у FastAPI. * ✅ Генерувати захищені токени. * ✅ Закривати ендпоінти від неавторизованих користувачів.
Інтрига на наступний урок: Зараз наші юзери живуть у словнику в пам'яті Python. Якщо перезавантажити сервер — вони зникнуть. Як зберегти їх назавжди? Наступного разу ми підключимо справжню базу даних SQL і навчимося "солити" паролі! 🧂
Це був CS50... тобто, це був ваш урок з FastAPI Security. Успіхів у коді!