Теперь мы углубимся в детали токенов, которые являются важнейшим механизмом в OAuth2.
Сегодня поговорим об Access Tokens и Refresh Tokens — двух ключевых видах токенов, которые обеспечивают доступ и его обновление.
Почему это важно? Без них вам придётся спрашивать пользователя логин и пароль каждые пять минут. А это, согласитесь, не очень весело.
OAuth2 перестал быть чем-то сверхъестественным в веб-разработке.
Практически каждый современный сервис, от API вашего любимого мессенджера до платёжных шлюзов, использует токены для управления доступом. Использование токенов позволяет:
- Избежать передачи логина и пароля с каждым запросом (спасибо за это безопасность).
- Разграничить доступ, предоставив токены с разными уровнями разрешений.
- Управлять временем жизни токенов, чтобы ограничить их "власть" над ресурсами.
Access Tokens: токены для доступа
Access Token — это цифровой ключ, который предоставляет клиенту доступ к защищённым ресурсам через API.
Это как пропуск на корпоративную вечеринку — предъявил его на входе, и ты внутри.
Access Token отправляется клиентом в каждом запросе к API. Обычно он добавляется в заголовок запроса в формате:
Authorization: Bearer <access_token>
Пример использования с библиотекой httpx:
import httpx
access_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6..."
headers = {"Authorization": f"Bearer {access_token}"}
response = httpx.get("https://api.example.com/protected-route", headers=headers)
print(response.json())
Время жизни (TTL, Time To Live)
Access Tokens имеют ограниченное время жизни, обычно от 5 до 30 минут.
Это делается для безопасности: даже если токен утечёт, злоумышленник сможет использовать его только в течение ограниченного времени.
Важно: после истечения срока действия токена сервер будет отклонять запросы с этим токеном, отправляя ошибку вроде 401 Unauthorized.
Refresh Tokens: обновляем доступ
Если Access Token — это пропуск на вечеринку, то Refresh Token — это ваш "привилегированный статус" для получения нового пропуска, если старый уже не действует. Он используется для выдачи нового Access Token, когда срок действия текущего токена истёк.
Refresh Tokens имеют гораздо большее время жизни (иногда дни или недели), что делает их удобным инструментом для автоматического обновления сессий пользователей.
Когда истекает срок действия Access Token, клиент может отправить Refresh Token на сервер, чтобы получить новый Access Token, не беспокоя пользователя. Типичный запрос выглядит так:
POST /token/refresh HTTP/1.1
Content-Type: application/json
{
"refresh_token": "e60efbb7-1bda-4e5b-9aca-..."
}
Ответ от сервера:
{
"access_token": "new-access-token-12345",
"refresh_token": "new-refresh-token-67890"
}
Пример реализации Refresh Token в FastAPI
from fastapi.security import OAuth2PasswordRequestForm
from fastapi import FastAPI, HTTPException, Depends
from datetime import datetime, timedelta
from jose import JWTError, jwt
# Конфигурации
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
REFRESH_TOKEN_EXPIRE_DAYS = 7
app = FastAPI()
# Модель пользователя (упрощённая)
fake_users_db = {
"johndoe": {
"username": "johndoe",
"hashed_password": "fakehashedpassword", # Используйте реальное хэширование!
}
}
def create_access_token(data: dict, expires_delta: timedelta):
to_encode = data.copy()
expire = datetime.utcnow() + expires_delta
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def create_refresh_token(data: dict):
expires_delta = timedelta(days=REFRESH_TOKEN_EXPIRE_DAYS)
return create_access_token(data, expires_delta)
@app.post("/token")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = fake_users_db.get(form_data.username)
if not user or user["hashed_password"] != form_data.password:
raise HTTPException(status_code=400, detail="Incorrect username or password")
# Генерация токенов
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token({"sub": user["username"]}, access_token_expires)
refresh_token = create_refresh_token({"sub": user["username"]})
return {"access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer"}
@app.post("/token/refresh")
async def refresh_token(refresh_token: str):
try:
payload = jwt.decode(refresh_token, SECRET_KEY, algorithms=[ALGORITHM])
username = payload.get("sub")
if username is None:
raise HTTPException(status_code=401, detail="Invalid refresh token")
# Создаём новый Access Token
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token({"sub": username}, access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}
except JWTError:
raise HTTPException(status_code=401, detail="Invalid refresh token")
Как хранить Refresh Tokens?
Refresh Tokens обычно хранятся более безопасно, чем Access Tokens. Хорошей практикой будет:
- На клиенте: хранить Refresh Token в
HttpOnlycookie (невидимый для JavaScript, чтобы защитить его от XSS-атак). - На сервере: можно сохранить Refresh Token в базе данных для проверки валидности (хотя это необязательно).
Риски и защита токенов
Риски- Утечка токена: если токен попадёт в чужие руки, злоумышленник сможет получить доступ к вашим ресурсам.
- Повторное использование Refresh Token: если злоумышленник украдёт Refresh Token, он сможет генерировать новые Access Tokens.
- HTTPs: всегда используйте HTTPS для передачи токенов.
- HttpOnly Cookies: храните токены в защищённых cookie.
- Короткое время жизни Access Token: уменьшайте TTL для Access Tokens, чтобы минимизировать потенциальный ущерб от их утечки.
- Ротация Refresh Token: создавайте новый Refresh Token каждый раз, когда создаётся Access Token, чтобы старый Refresh Token становился недействительным.
Пример использования токенов в реальных приложениях
Допустим, вы создаёте приложение для заказов пиццы через FastAPI.
Пользователь заходит в приложение, логинится, и сервер выдаёт Access и Refresh Tokens.
Access Token позволяет ему заказывать пиццу, а Refresh Token обеспечивает автоматическую подзарядку Access Token, пока пользователь наслаждается своим заказом.
Теперь представьте, что токены украли. Если вы настроили механизм ротации и короткого времени жизни, злоумышленники не смогут использовать украденные токены для дальнейших запросов. Бинго, вы спасли чей-то "пицца-день"!
На этом всё на сегодня. В следующей лекции мы углубимся в JWT и увидим, как они используются для генерации и верификации токенов.
Будьте готовы, будет весело и интересно!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ