Перейти к содержимому

Лекция 5. Аутентификация и авторизация в веб-приложениях


1. Введение

Что такое аутентификация и авторизация

Аутентификация (Authentication) — процесс проверки личности пользователя, подтверждение того, что пользователь действительно является тем, за кого себя выдает. Отвечает на вопрос: “Кто вы?”

Авторизация (Authorization) — процесс определения прав доступа пользователя к ресурсам и операциям. Отвечает на вопрос: “Что вам разрешено делать?”

Разница:

  • Аутентификация — проверка личности (логин/пароль, биометрия, токены)
  • Авторизация — проверка прав (роли, разрешения, ACL)

Зачем это нужно

Веб-приложения должны:

  • Защищать данные пользователей от несанкционированного доступа
  • Контролировать доступ к различным функциям и ресурсам
  • Отслеживать действия пользователей для аудита и безопасности
  • Обеспечивать персонализацию интерфейса и данных

2. Основные концепции

2.1. Учетные данные (Credentials)

Учетные данные — информация, используемая для подтверждения личности пользователя.

Типы учетных данных:

  1. Что-то, что вы знаете (Knowledge-based)

    • Пароль
    • PIN-код
    • Ответы на секретные вопросы
  2. Что-то, что у вас есть (Possession-based)

    • Физический токен
    • Смартфон (для SMS/приложений-аутентификаторов)
    • USB-ключ (YubiKey)
  3. Что-то, чем вы являетесь (Biometric)

    • Отпечаток пальца
    • Распознавание лица
    • Голос

Многофакторная аутентификация (MFA) — использование двух или более факторов для повышения безопасности.

2.2. Сессии

Сессия — период времени, в течение которого пользователь остается аутентифицированным после входа в систему.

Характеристики сессий:

  • Имеют уникальный идентификатор (Session ID)
  • Хранят состояние пользователя на сервере или в токене
  • Имеют время жизни (TTL — Time To Live)
  • Могут быть прерваны при выходе или истечении времени

2.3. Токены

Токен — строка символов, которая представляет права доступа пользователя.

Типы токенов:

  • Access Token — для доступа к защищенным ресурсам
  • Refresh Token — для обновления access token без повторной аутентификации
  • ID Token — содержит информацию о пользователе (в OAuth 2.0/OpenID Connect)

3. Методы аутентификации

3.1. Базовая HTTP-аутентификация (Basic Auth)

Принцип работы:

  1. Клиент отправляет запрос без учетных данных
  2. Сервер отвечает 401 Unauthorized с заголовком WWW-Authenticate: Basic
  3. Клиент кодирует username:password в Base64 и отправляет в заголовке Authorization: Basic <encoded>

Пример:

GET /api/users HTTP/1.1
Host: example.com
Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=

Преимущества:

  • Простота реализации
  • Поддержка всеми браузерами и HTTP-клиентами

Недостатки:

  • Пароль передается в каждом запросе (даже в Base64)
  • Нет встроенного механизма выхода
  • Уязвимость к перехвату трафика (требуется HTTPS)

Когда использовать:

  • Внутренние API
  • Простые системы без высоких требований безопасности
  • Прототипирование

3.2. Аутентификация на основе сессий (Session-based)

Принцип работы:

  1. Пользователь отправляет логин и пароль
  2. Сервер проверяет учетные данные
  3. Сервер создает сессию и сохраняет Session ID в cookie или localStorage
  4. Клиент отправляет Session ID в каждом последующем запросе
  5. Сервер проверяет Session ID и восстанавливает данные пользователя

Пример реализации (FastAPI):

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from pydantic import BaseModel
import secrets
from datetime import datetime, timedelta
app = FastAPI()
security = HTTPBasic()
# Хранилище сессий (в продакшене использовать Redis или БД)
sessions = {}
class LoginRequest(BaseModel):
username: str
password: str
class User(BaseModel):
id: int
username: str
email: str
# Упрощенная проверка пароля (в реальности использовать хеширование)
def verify_password(username: str, password: str) -> bool:
# Здесь должна быть проверка с БД
return username == "admin" and password == "secret"
def create_session(user: User) -> str:
session_id = secrets.token_urlsafe(32)
sessions[session_id] = {
"user": user,
"created_at": datetime.now(),
"expires_at": datetime.now() + timedelta(hours=24)
}
return session_id
def get_session(session_id: str):
if session_id not in sessions:
return None
session = sessions[session_id]
if datetime.now() > session["expires_at"]:
del sessions[session_id]
return None
return session
@app.post("/login")
def login(credentials: LoginRequest):
if not verify_password(credentials.username, credentials.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
user = User(id=1, username=credentials.username, email="user@example.com")
session_id = create_session(user)
return {"session_id": session_id, "user": user}
@app.get("/profile")
def get_profile(session_id: str = Depends(lambda: None)):
if not session_id:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Session required"
)
session = get_session(session_id)
if not session:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid or expired session"
)
return session["user"]

Преимущества:

  • Сервер контролирует сессии
  • Можно легко отозвать доступ
  • Безопасное хранение данных на сервере

Недостатки:

  • Требует хранения состояния на сервере
  • Проблемы с масштабированием (нужен общий хранилище сессий)
  • Уязвимость к CSRF-атакам

Когда использовать:

  • Традиционные веб-приложения
  • Когда нужен контроль над сессиями на сервере
  • Когда используется монолитная архитектура

3.3. JWT (JSON Web Token)

JWT — открытый стандарт (RFC 7519) для создания токенов доступа, основанный на JSON.

Структура JWT:

header.payload.signature

Пример токена:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Расшифровка:

  1. Header (Base64):
{
"alg": "HS256",
"typ": "JWT"
}
  1. Payload (Base64):
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
  1. Signature:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)

Пример реализации (FastAPI с PyJWT):

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from datetime import datetime, timedelta
import jwt
app = FastAPI()
security = HTTPBearer()
SECRET_KEY = "your-secret-key-here" # В продакшене использовать переменные окружения
ALGORITHM = "HS256"
class LoginRequest(BaseModel):
username: str
password: str
class User(BaseModel):
id: int
username: str
email: str
def verify_password(username: str, password: str) -> bool:
return username == "admin" and password == "secret"
def create_access_token(user: User) -> str:
payload = {
"sub": str(user.id),
"username": user.username,
"email": user.email,
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(hours=1)
}
token = jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)
return token
def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
try:
token = credentials.credentials
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
return payload
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Token expired"
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid token"
)
@app.post("/login")
def login(credentials: LoginRequest):
if not verify_password(credentials.username, credentials.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid credentials"
)
user = User(id=1, username=credentials.username, email="user@example.com")
access_token = create_access_token(user)
return {
"access_token": access_token,
"token_type": "bearer",
"user": user
}
@app.get("/profile")
def get_profile(payload: dict = Depends(verify_token)):
return {
"id": payload["sub"],
"username": payload["username"],
"email": payload["email"]
}

Стандартные поля JWT (Claims):

ПолеНазваниеОписание
issIssuerКто выдал токен
subSubjectИдентификатор пользователя
audAudienceДля кого предназначен токен
expExpiration TimeВремя истечения (Unix timestamp)
nbfNot BeforeНе использовать до (Unix timestamp)
iatIssued AtВремя выдачи (Unix timestamp)
jtiJWT IDУникальный идентификатор токена

Преимущества:

  • Stateless (не требует хранения на сервере)
  • Масштабируемость
  • Поддержка микросервисной архитектуры
  • Можно передавать дополнительную информацию

Недостатки:

  • Токен нельзя отозвать до истечения срока (нужен blacklist)
  • Больший размер, чем Session ID
  • Требует защиты секретного ключа

Когда использовать:

  • REST API
  • Микросервисная архитектура
  • Мобильные приложения
  • SPA (Single Page Applications)

3.4. OAuth 2.0

OAuth 2.0 — протокол авторизации, который позволяет приложениям получать ограниченный доступ к учетным записям пользователей на других сервисах.

Роли в OAuth 2.0:

  1. Resource Owner (Владелец ресурса) — пользователь
  2. Client (Клиент) — приложение, запрашивающее доступ
  3. Authorization Server (Сервер авторизации) — выдает токены
  4. Resource Server (Сервер ресурсов) — API, защищенный токенами

Типы потоков (Grant Types):

Authorization Code Flow (наиболее безопасный)

1. Клиент перенаправляет пользователя на Authorization Server
2. Пользователь авторизуется и дает согласие
3. Authorization Server перенаправляет обратно с authorization code
4. Клиент обменивает code на access token
5. Клиент использует access token для доступа к ресурсам

Client Credentials Flow (для сервис-сервис взаимодействия)

1. Клиент отправляет свои credentials напрямую
2. Authorization Server выдает access token
3. Клиент использует токен для доступа к ресурсам

Пример использования OAuth 2.0 (FastAPI):

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
from datetime import datetime, timedelta
import jwt
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET_KEY = "your-secret-key"
ALGORITHM = "HS256"
class User(BaseModel):
username: str
email: str
hashed_password: str
# Упрощенная БД пользователей
fake_users_db = {
"admin": {
"username": "admin",
"email": "admin@example.com",
"hashed_password": "hashed_secret" # В реальности использовать bcrypt
}
}
def create_access_token(data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
@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=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=30)
access_token = create_access_token(
data={"sub": user["username"]}, expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(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=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
except jwt.InvalidTokenError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
user = fake_users_db.get(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid authentication credentials"
)
return user

Преимущества:

  • Стандартизированный протокол
  • Делегирование авторизации
  • Поддержка сторонних провайдеров (Google, GitHub, Facebook)
  • Гибкость (несколько типов потоков)

Когда использовать:

  • Интеграция с внешними сервисами
  • Единый вход (SSO — Single Sign-On)
  • Когда нужно делегировать авторизацию

4. Хранение паролей

4.1. Хеширование паролей

Никогда не храните пароли в открытом виде!

Правильный подход:

  1. Использовать криптографические хеш-функции
  2. Добавлять соль (salt) для защиты от rainbow tables
  3. Использовать адаптивные алгоритмы (bcrypt, argon2, scrypt)

Пример с bcrypt (Python):

import bcrypt
# Хеширование пароля при регистрации
def hash_password(password: str) -> str:
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed.decode('utf-8')
# Проверка пароля при входе
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(
plain_password.encode('utf-8'),
hashed_password.encode('utf-8')
)
# Использование
password = "my_secret_password"
hashed = hash_password(password)
print(f"Hashed: {hashed}")
# Проверка
is_valid = verify_password("my_secret_password", hashed)
print(f"Valid: {is_valid}") # True
is_invalid = verify_password("wrong_password", hashed)
print(f"Valid: {is_invalid}") # False

Алгоритмы хеширования:

АлгоритмОписаниеРекомендация
MD5, SHA-1Устаревшие, небезопасные❌ Не использовать
SHA-256, SHA-512Быстрые, но не адаптивные⚠️ Только с солью
bcryptАдаптивный, медленный✅ Рекомендуется
argon2Современный, победитель PHC✅ Рекомендуется
scryptАдаптивный, требует памяти✅ Рекомендуется

5. Авторизация и управление доступом

5.1. Роли и разрешения (RBAC)

RBAC (Role-Based Access Control) — модель контроля доступа, основанная на ролях пользователей.

Пример реализации:

from enum import Enum
from fastapi import Depends, HTTPException, status
class Role(str, Enum):
ADMIN = "admin"
USER = "user"
MODERATOR = "moderator"
class Permission(str, Enum):
READ_BOOKS = "read:books"
WRITE_BOOKS = "write:books"
DELETE_BOOKS = "delete:books"
MANAGE_USERS = "manage:users"
# Маппинг ролей на разрешения
ROLE_PERMISSIONS = {
Role.ADMIN: [
Permission.READ_BOOKS,
Permission.WRITE_BOOKS,
Permission.DELETE_BOOKS,
Permission.MANAGE_USERS
],
Role.MODERATOR: [
Permission.READ_BOOKS,
Permission.WRITE_BOOKS,
Permission.DELETE_BOOKS
],
Role.USER: [
Permission.READ_BOOKS
]
}
def require_permission(permission: Permission):
def check_permission(payload: dict = Depends(verify_token)):
user_role = Role(payload.get("role", "user"))
user_permissions = ROLE_PERMISSIONS.get(user_role, [])
if permission not in user_permissions:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail=f"Permission '{permission}' required"
)
return payload
return check_permission
@app.delete("/books/{book_id}")
def delete_book(
book_id: str,
user: dict = Depends(require_permission(Permission.DELETE_BOOKS))
):
# Логика удаления книги
return {"message": f"Book {book_id} deleted"}

5.2. ACL (Access Control List)

ACL — список контроля доступа, определяющий права для конкретных ресурсов и пользователей.

Пример:

class Resource:
def __init__(self, id: str, owner_id: str):
self.id = id
self.owner_id = owner_id
# ACL для ресурса
resource_acl = {
"book_123": {
"owner": "user_1",
"readers": ["user_2", "user_3"],
"editors": ["user_2"]
}
}
def check_resource_access(resource_id: str, user_id: str, action: str) -> bool:
acl = resource_acl.get(resource_id)
if not acl:
return False
if acl["owner"] == user_id:
return True # Владелец имеет все права
if action == "read" and user_id in acl.get("readers", []):
return True
if action == "write" and user_id in acl.get("editors", []):
return True
return False

5.3. ABAC (Attribute-Based Access Control)

ABAC — контроль доступа на основе атрибутов пользователя, ресурса и контекста.

Пример:

def check_access(user: dict, resource: dict, action: str) -> bool:
# Правила доступа
if user["department"] == resource["department"]:
return True
if user["level"] >= 5 and action == "read":
return True
if resource["public"] and action == "read":
return True
return False

6. Безопасность

6.1. Основные угрозы

SQL Injection

Защита: Использовать параметризованные запросы, ORM

# ❌ Плохо
query = f"SELECT * FROM users WHERE username = '{username}'"
# ✅ Хорошо
query = "SELECT * FROM users WHERE username = %s"
cursor.execute(query, (username,))

XSS (Cross-Site Scripting)

Защита: Экранирование пользовательского ввода, CSP заголовки

from html import escape
user_input = "<script>alert('XSS')</script>"
safe_output = escape(user_input) # &lt;script&gt;alert(&#x27;XSS&#x27;)&lt;/script&gt;

CSRF (Cross-Site Request Forgery)

Защита: CSRF токены, SameSite cookies

from fastapi import FastAPI, Request
from fastapi.middleware.csrf import CSRFMiddleware
app = FastAPI()
app.add_middleware(CSRFMiddleware, secret="your-secret-key")

Session Hijacking

Защита: HTTPS, Secure и HttpOnly флаги для cookies, регулярная ротация токенов

Brute Force

Защита: Rate limiting, CAPTCHA, блокировка после нескольких неудачных попыток

from fastapi import FastAPI, Request
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
limiter = Limiter(key_func=get_remote_address)
app = FastAPI()
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.post("/login")
@limiter.limit("5/minute")
def login(request: Request):
# Логика входа
pass

6.2. Заголовки безопасности

Рекомендуемые заголовки:

from fastapi import FastAPI
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from starlette.middleware.cors import CORSMiddleware
app = FastAPI()
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["https://example.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Trusted Host
app.add_middleware(TrustedHostMiddleware, allowed_hosts=["example.com"])
# Дополнительные заголовки безопасности
@app.middleware("http")
async def add_security_headers(request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
response.headers["Content-Security-Policy"] = "default-src 'self'"
return response

7. Best Practices

7.1. Рекомендации по аутентификации

Делайте:

  • Используйте HTTPS для всех запросов с учетными данными
  • Храните пароли только в хешированном виде
  • Используйте сильные алгоритмы хеширования (bcrypt, argon2)
  • Реализуйте rate limiting для эндпойнтов входа
  • Используйте короткое время жизни для access tokens
  • Реализуйте refresh tokens для продления сессий
  • Валидируйте все входные данные
  • Логируйте попытки входа (успешные и неудачные)

Не делайте:

  • Не храните пароли в открытом виде
  • Не используйте слабые алгоритмы хеширования (MD5, SHA-1)
  • Не передавайте токены в URL (используйте заголовки)
  • Не используйте предсказуемые токены
  • Не храните секретные ключи в коде
  • Не игнорируйте истечение токенов

7.2. Рекомендации по авторизации

Делайте:

  • Принцип наименьших привилегий (Least Privilege)
  • Проверяйте права доступа на каждом защищенном эндпойнте
  • Используйте централизованную систему управления ролями
  • Регулярно проверяйте и обновляйте права доступа
  • Логируйте все действия, требующие авторизации

Не делайте:

  • Не полагайтесь только на клиентскую проверку прав
  • Не давайте избыточных прав пользователям
  • Не забывайте проверять права при изменении ресурсов

7.3. Хранение секретов

Используйте переменные окружения:

import os
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
secret_key: str
database_url: str
jwt_algorithm: str = "HS256"
jwt_expiration_hours: int = 24
class Config:
env_file = ".env"
settings = Settings()

Файл .env:

SECRET_KEY=your-super-secret-key-here
DATABASE_URL=postgresql://user:password@localhost/dbname
JWT_ALGORITHM=HS256
JWT_EXPIRATION_HOURS=24

Важно: Добавьте .env в .gitignore!


8. Пример полной реализации

Полный пример системы аутентификации и авторизации:

from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel, EmailStr
from datetime import datetime, timedelta
from typing import Optional
import jwt
import bcrypt
from enum import Enum
app = FastAPI(title="Auth API")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
SECRET_KEY = "your-secret-key-change-in-production"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
class Role(str, Enum):
ADMIN = "admin"
USER = "user"
class UserCreate(BaseModel):
username: str
email: EmailStr
password: str
class User(BaseModel):
id: int
username: str
email: str
role: Role
hashed_password: str
class Token(BaseModel):
access_token: str
token_type: str
# Упрощенная БД (в реальности использовать PostgreSQL)
users_db = {}
next_user_id = 1
def hash_password(password: str) -> str:
salt = bcrypt.gensalt()
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
return hashed.decode('utf-8')
def verify_password(plain_password: str, hashed_password: str) -> bool:
return bcrypt.checkpw(
plain_password.encode('utf-8'),
hashed_password.encode('utf-8')
)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
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)) -> User:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
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 jwt.InvalidTokenError:
raise credentials_exception
user = users_db.get(username)
if user is None:
raise credentials_exception
return user
def require_role(required_role: Role):
def role_checker(current_user: User = Depends(get_current_user)):
if current_user.role != required_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Insufficient permissions"
)
return current_user
return role_checker
@app.post("/register", response_model=User)
def register(user_data: UserCreate):
if user_data.username in users_db:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Username already registered"
)
global next_user_id
hashed_password = hash_password(user_data.password)
user = User(
id=next_user_id,
username=user_data.username,
email=user_data.email,
role=Role.USER,
hashed_password=hashed_password
)
users_db[user_data.username] = user
next_user_id += 1
# Не возвращаем пароль
user_dict = user.model_dump()
del user_dict["hashed_password"]
return User(**user_dict)
@app.post("/token", response_model=Token)
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
user = users_db.get(form_data.username)
if not user or not verify_password(form_data.password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect username or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(
data={"sub": user.username, "role": user.role.value},
expires_delta=access_token_expires
)
return {"access_token": access_token, "token_type": "bearer"}
@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_user)):
user_dict = current_user.model_dump()
del user_dict["hashed_password"]
return user_dict
@app.get("/admin/users")
async def list_all_users(admin: User = Depends(require_role(Role.ADMIN))):
return [
{k: v for k, v in user.model_dump().items() if k != "hashed_password"}
for user in users_db.values()
]

Заключение

Аутентификация и авторизация — критически важные компоненты любого веб-приложения. Правильная реализация обеспечивает:

  • Безопасность данных пользователей
  • Контроль доступа к ресурсам
  • Масштабируемость системы
  • Соответствие требованиям безопасности

Ключевые моменты:

  1. Выбор метода аутентификации зависит от архитектуры приложения

    • Сессии для традиционных веб-приложений
    • JWT для API и микросервисов
    • OAuth 2.0 для интеграций
  2. Безопасность паролей — всегда используйте хеширование с солью

  3. Авторизация — реализуйте RBAC или ABAC в зависимости от требований

  4. Best Practices — следуйте рекомендациям по безопасности и не изобретайте велосипед

  5. Мониторинг — логируйте события аутентификации и авторизации

Помните: безопасность — это не фича, которую можно добавить потом, а фундаментальный аспект разработки веб-приложений.