You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FastAPI结合Django时,如何安全实现基于会话的身份验证?

问题:FastAPI + Django 安全集成身份验证与授权

架构选型依据

  • 选择FastAPI:原生支持WebSocket,自带Pydantic校验与自动文档生成功能
  • 选择Django:复用其内置的身份验证、授权机制及ORM
  • 未选用Django-ninja:不支持WebSocket
  • 未选用Django+DRF+Channels:更偏好FastAPI的API特性与原生WebSocket支持

当前困境

我们希望用Django的用户会话与权限系统保护FastAPI端点,但目前直接创建Session对象的实现绕过了Django安全中间件,存在安全风险,需要更规范、安全的实现方案。

当前代码架构:

  1. main.py:初始化FastAPI并挂载Django应用,包含API路由
  2. router.py:定义FastAPI端点,尝试用Django会话做验证,但登录与受保护端点的实现存在安全隐患

现有问题代码示例:

## main.py (FastAPI)

import os

from django.conf import settings
from django.core.asgi import get_asgi_application
from django.apps import apps

from fastapi import FastAPI

from service_using_websockets.endpoints import router

# Setup FastAPI w/ and include the api router
api = FastAPI()
api.include_router(router, prefix="/api")

# Mount the Django backend application to the api
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
apps.populate(settings.INSTALLED_APPS)
api.mount("/backend", get_asgi_application())


## router.py (FastAPI, using Django auth)

from fastapi import APIRouter, Response
from django.contrib.sessions.models import Session
from django.contrib.auth import authenticate
from django.utils import timezone
import datetime

router = APIRouter()

@router.post("/login")
def login(credentials: HTTPBasicCredentials = Depends(security)):
    user = authenticate(username=credentials.username, password=credentials.password)
    
    # User authenticated w/ Django, now create a session
    if user is not None:
        # TODO: this works, but might be bad security-wise as we
        # are working around Django's security middleware this way
        session = Session()
        session.session_key = Session.objects.generate_session_key()
        session.session_data = {}  # You can store additional session data here
        session.expire_date = timezone.now() + datetime.timedelta(days=1)  # Set session expiry
        session.save()

        # Set the session ID in the cookie
        response.set_cookie(key="sessionid", value=session.session_key, httponly=True)
        return {"message": "User logged in successfully"}
    else:
        return {"message": "Invalid username or password"}

@router.get("/protected-endpoint")
def example_protected_endpoint(request: Request):
    session_id = request.cookies.get("session_id")
    if session_id is None or int(session_id) not in sessions_in_db:
        raise HTTPException(
            status_code=401,
            detail="Login and get a valid session",
        )
    # Get the user from the session
    user = get_user_from_session(int(session_id))
    # ... do some endpoint logic for this user

安全规范的实现方案

核心思路是让Django的安全中间件全权处理会话的创建与验证,而非手动操作Session模型,具体步骤如下:

1. 复用Django内置登录逻辑创建安全会话

替换手动创建Session的代码,调用Django原生的login函数,它会自动处理会话加密、安全标记等中间件逻辑:

from fastapi import APIRouter, Response, Depends, HTTPException, Request
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from django.contrib.auth import authenticate, login as django_login
from django.contrib.auth.models import User
from django.contrib.sessions.middleware import SessionMiddleware
from django.http import HttpRequest
from django.conf import settings

router = APIRouter()
security = HTTPBasic()

# 把FastAPI请求转换为Django可处理的请求对象
def get_django_request(request: Request) -> HttpRequest:
    django_request = HttpRequest()
    django_request.method = request.method
    django_request.path = request.url.path
    django_request.META = dict(request.scope)
    django_request.COOKIES = request.cookies
    django_request.session = {}
    # 应用Django会话中间件处理请求
    session_middleware = SessionMiddleware(lambda req: None)
    session_middleware.process_request(django_request)
    return django_request

@router.post("/login")
def login(
    credentials: HTTPBasicCredentials = Depends(security),
    django_request: HttpRequest = Depends(get_django_request),
    response: Response = Response()
):
    user = authenticate(username=credentials.username, password=credentials.password)
    if user is not None:
        # 用Django内置函数创建安全会话
        django_login(django_request, user)
        django_request.session.save()
        # 严格遵循Django配置设置会话Cookie
        response.set_cookie(
            key="sessionid",
            value=django_request.session.session_key,
            httponly=True,
            secure=settings.SESSION_COOKIE_SECURE,
            samesite=settings.SESSION_COOKIE_SAMESITE,
            max_age=settings.SESSION_COOKIE_AGE,
            path=settings.SESSION_COOKIE_PATH
        )
        return {"message": "登录成功"}
    raise HTTPException(status_code=401, detail="用户名或密码错误")

2. 验证会话并获取已授权用户

创建依赖项,复用Django身份验证中间件来验证会话合法性,确保所有安全检查生效:

from django.contrib.auth.middleware import AuthenticationMiddleware

def get_current_user(django_request: HttpRequest = Depends(get_django_request)) -> User:
    # 应用Django身份验证中间件
    auth_middleware = AuthenticationMiddleware(lambda req: None)
    auth_middleware.process_request(django_request)
    if not django_request.user.is_authenticated:
        raise HTTPException(status_code=401, detail="未授权,请先登录")
    return django_request.user

@router.get("/protected-endpoint")
def example_protected_endpoint(current_user: User = Depends(get_current_user)):
    # 直接使用已验证的用户对象
    return {"user": current_user.username, "message": "访问受保护端点成功"}

3. 优化FastAPI与Django的初始化顺序

确保Django先完成配置加载,避免手动调用apps.populate:

import os
from django.conf import settings
from django.core.asgi import get_asgi_application
from fastapi import FastAPI
from service_using_websockets.endpoints import router

# 先初始化Django,再创建FastAPI应用
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "backend.settings")
django_asgi_app = get_asgi_application()

api = FastAPI()
api.include_router(router, prefix="/api")
api.mount("/backend", django_asgi_app)

关键安全注意事项

  • 绝不手动操作Session模型,始终通过Django的login函数和中间件处理会话
  • 严格遵循settings.py中的会话安全配置(如SESSION_COOKIE_SECURE、SESSION_COOKIE_HTTPONLY)
  • 依赖Django身份验证中间件完成用户验证,确保会话过期、CSRF保护等安全机制生效

内容的提问来源于stack exchange,提问作者KoenCuijp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 06:56:05