← Назад к вопросам

В чем разница между ошибкой авторизации и недостаточных прав?

1.0 Junior🔥 191 комментариев
#REST API и HTTP#Безопасность

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Ошибка авторизации vs Недостаточные права

Авторизация (Authentication) vs Аутентификация

Сначала важно различать два понятия:

  • Аутентификация (Authentication) — это процесс идентификации пользователя (кто вы?)
  • Авторизация (Authorization) — это процесс проверки прав доступа (что вы можете делать?)

1. Ошибка авторизации (Authentication failure) — 401 Unauthorized

Ошибка авторизации возникает, когда система не может идентифицировать пользователя или пользователь не был проаутентифицирован.

Это означает: система не знает, кто вы.

Примеры ошибок авторизации:

  • Вы не передали токен доступа
  • Токен истёк или недействителен
  • Неверный логин/пароль
  • Сессия завершена
  • Подпись токена невалидна
from fastapi import FastAPI, HTTPException, status, Depends
from fastapi.security import HTTPBearer
from datetime import datetime, timedelta
import jwt

app = FastAPI()
security = HTTPBearer()

SECRET_KEY = "your-secret-key"

def verify_token(credentials):
    """Проверка токена — ошибка авторизации если токен невалидный"""
    try:
        token = credentials.credentials
        payload = jwt.decode(token, SECRET_KEY, algorithms=["HS256"])
        user_id = payload.get("sub")
        if user_id is None:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid authentication credentials",
                headers={"WWW-Authenticate": "Bearer"},
            )
        return user_id
    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token expired",  # Ошибка авторизации!
            headers={"WWW-Authenticate": "Bearer"},
        )
    except jwt.InvalidTokenError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",  # Ошибка авторизации!
            headers={"WWW-Authenticate": "Bearer"},
        )

@app.get("/protected")
async def protected_route(user_id: str = Depends(verify_token)):
    return {"message": f"Hello, user {user_id}"}

# Сценарии ошибки авторизации:
# 1. GET /protected → 401 (нет токена)
# 2. GET /protected?token=invalid → 401 (невалидный токен)
# 3. GET /protected?token=expired → 401 (истёкший токен)

HTTP код: 401 Unauthorized

from fastapi import HTTPException, status

@app.get("/api/data")
async def get_data(token: str = None):
    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing authentication token"
        )
    
    # Проверка валидности токена
    if not is_valid_token(token):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token"
        )
    
    return {"data": "sensitive"}

2. Недостаточные права (Authorization failure) — 403 Forbidden

Ошибка недостаточных прав возникает, когда система знает, кто вы, но вам не разрешено выполнять это действие.

Это означает: система знает, кто вы, но не доверяет вам выполнять это.

Примеры ошибок недостаточных прав:

  • Вы пользователь, а операция требует администратора
  • Вы не владелец ресурса
  • Ваш план подписки не включает эту функцию
  • Ваша роль не имеет необходимых разрешений
  • Доступ к ресурсу запрещён политикой
from enum import Enum
from typing import List

class UserRole(str, Enum):
    ADMIN = "admin"
    MODERATOR = "moderator"
    USER = "user"

class User:
    def __init__(self, id: str, name: str, role: UserRole):
        self.id = id
        self.name = name
        self.role = role

# Текущий пользователь (уже аутентифицирован)
current_user = User("123", "John", UserRole.USER)

def require_admin(user: User):
    """Проверка прав — ошибка авторизации если прав недостаточно"""
    if user.role != UserRole.ADMIN:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Insufficient permissions. Admin role required."
        )
    return user

@app.delete("/admin/users/{user_id}")
async def delete_user(user_id: str, admin: User = Depends(require_admin)):
    # Если пользователь — не админ, получит 403 Forbidden
    return {"deleted": user_id}

# Сценарий:
# 1. Пользователь с ролью USER попытается удалить пользователя
# 2. Система знает, кто это (аутентификация ✅)
# 3. Но у него нет прав (авторизация ❌)
# 4. Ответ: 403 Forbidden

HTTP код: 403 Forbidden

@app.get("/api/admin/stats")
async def admin_stats(user_id: str = Depends(verify_token)):
    user = get_user(user_id)
    
    # Аутентификация успешна (мы знаем пользователя)
    # Но проверяем права:
    if user.role != "admin":
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You don't have permission to access admin stats"
        )
    
    return {"stats": "admin data"}

Таблица сравнения

АспектАвторизация (401)Недостаточные права (403)
ВопросКто вы?Что вы можете делать?
ПроблемаСистема не знает вашу личностьСистема знает вас, но запретила действие
ПричинаТокен отсутствует, истёк, невалидныйНет нужной роли/разрешения
HTTP код401 Unauthorized403 Forbidden
РешениеАутентифицируйтесь (войдите)Запросите доступ или свяжитесь с администратором
ЗаголовкиWWW-Authenticateнет
ПримерJWT токен истёкВы не администратор

Практический пример: API эндпоинты

from fastapi import APIRouter, HTTPException, status, Depends
from fastapi.security import HTTPBearer

router = APIRouter(prefix="/api/v1")
security = HTTPBearer()

class Post:
    def __init__(self, id: str, owner_id: str, content: str):
        self.id = id
        self.owner_id = owner_id
        self.content = content

# Фейковая БД
posts = {
    "1": Post("1", "alice", "Alice's post"),
    "2": Post("2", "bob", "Bob's post"),
}

def get_current_user(credentials) -> str:
    """Получить текущего пользователя (аутентификация)"""
    token = credentials.credentials
    
    # Проверка токена
    if not token.startswith("user_"):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token",  # 401 — не знаем, кто вы
        )
    
    user_id = token.replace("user_", "")
    return user_id

@router.delete("/posts/{post_id}")
async def delete_post(
    post_id: str,
    user_id: str = Depends(get_current_user)
):
    """Удалить пост"""
    
    # Сценарий 1: Токен отсутствует или невалиден
    # → 401 Unauthorized (ошибка авторизации)
    # Зависимость get_current_user выбросит исключение
    
    post = posts.get(post_id)
    if not post:
        return {"error": "Post not found"}
    
    # Сценарий 2: Токен валиден, но пользователь не владелец
    if post.owner_id != user_id:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="You can only delete your own posts"  # 403 — прав недостаточно
        )
    
    # Сценарий 3: Успешно
    del posts[post_id]
    return {"deleted": post_id}

# Примеры запросов:
# 1. curl -H "Authorization: Bearer missing" http://localhost:8000/api/v1/posts/1
#    → 401 Unauthorized (отсутствует валидный токен)
#
# 2. curl -H "Authorization: Bearer user_bob" http://localhost:8000/api/v1/posts/1
#    → 403 Forbidden (Bob не владелец поста Alice)
#
# 3. curl -H "Authorization: Bearer user_alice" http://localhost:8000/api/v1/posts/1
#    → 200 OK (Alice может удалить свой пост)

Лучшие практики

Всегда отличай ошибки:

from typing import Optional

class AuthenticationError(Exception):
    """401 — проблема с идентификацией"""
    pass

class AuthorizationError(Exception):
    """403 — проблема с правами"""
    pass

def secure_operation(user_id: Optional[str], action: str):
    # 1. Проверка аутентификации
    if not user_id:
        raise AuthenticationError("User not authenticated")  # 401
    
    # 2. Проверка авторизации
    user = get_user(user_id)
    if action == "delete_user" and user.role != "admin":
        raise AuthorizationError("Admin role required")  # 403
    
    # 3. Выполнение операции
    return perform_action(user_id, action)

# Обработка в Flask/FastAPI:
@app.errorhandler(AuthenticationError)
def handle_auth_error(e):
    return {"error": str(e)}, 401

@app.errorhandler(AuthorizationError)
def handle_authz_error(e):
    return {"error": str(e)}, 403

Заключение

  • 401 Unauthorized — система не знает вашу личность (нет токена, истёк, невалидный)
  • 403 Forbidden — система знает вас, но вам запретили (нет прав)
  • Оба нужны для безопасности, но служат разным целям
  • Всегда проверяй сначала аутентификацию, потом авторизацию
В чем разница между ошибкой авторизации и недостаточных прав? | PrepBro