FastAPI结合Django时,如何安全实现基于会话的身份验证?
问题:FastAPI + Django 安全集成身份验证与授权
架构选型依据
- 选择FastAPI:原生支持WebSocket,自带Pydantic校验与自动文档生成功能
- 选择Django:复用其内置的身份验证、授权机制及ORM
- 未选用Django-ninja:不支持WebSocket
- 未选用Django+DRF+Channels:更偏好FastAPI的API特性与原生WebSocket支持
当前困境
我们希望用Django的用户会话与权限系统保护FastAPI端点,但目前直接创建Session对象的实现绕过了Django安全中间件,存在安全风险,需要更规范、安全的实现方案。
当前代码架构:
main.py:初始化FastAPI并挂载Django应用,包含API路由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
相关产品推荐
相关产品推荐

