fastapi: 管理后台:用中间件判断是否登录,有需要登录才能访问的页面直接跳转登录页
一,代码
middleware的代码:
# app/admin/middleware/auth_middleware.py
from urllib.parse import quote_plus
from fastapi import FastAPI, Request, Depends
from sqlalchemy import select
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse, RedirectResponse
from app.core.database import get_db, AsyncSessionLocal
from app.core.jwt import admin_login_optional, get_current_user_from_cookie
from app.models import User
# 白名单列表,管理后台:登录和注册页面
PUBLIC_PATHS = {"/admin/account/login", "/admin/account/register"}
# 中间件:全自动解析登录态
# 1. 定义中间件类,必须继承 BaseHTTPMiddleware
class CustomAuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self,
request: Request,
call_next,
):
# 从cookie得到用户信息
current_user = await get_current_user_from_cookie(request)
# 如果用户未登录
if current_user is None:
#判断url是否必须登录
# 过滤白名单,无需登录
if request.url.path in PUBLIC_PATHS:
# 将结果挂载到 request.state,方便后续调用
request.state.user = None
request.state.is_authenticated = False
response = await call_next(request)
return response
else: # 需要登录
# 得到当前页地址
# 1. 获取用户当前访问的相对完整路径+参数 (例如: /profile?template_id=123&mode=edit)
current_path = request.url.path
if request.url.query:
current_path += f"?{request.url.query}"
# 2. 🌟 生产核心:对整个路径进行安全 URL 编码
# /profile?template_id=123 --> %2Fprofile%3Ftemplate_id%3D123
encoded_next = quote_plus(current_path)
# 3. 拼接后跳转。此时 URL 变为:/login?next=%2Fprofile%3Ftemplate_id%3D123
login_path = "/admin/account/login"
login_url = f"{login_path}?next={encoded_next}"
return RedirectResponse(url=login_url, status_code=303)
# 生产环境核心:直接使用全局工厂创建独立的、短暂的会话,获取用户的完整信息
try:
async with AsyncSessionLocal() as db:
# 1. 查询用户是否存在
stmt = select(User).where(User.username == current_user['username'])
result = await db.execute(stmt)
user = result.scalar_one_or_none()
if user is None:
cur_user = None
else:
cur_user = {
'username':user.username,
'nickname': user.nickname,
}
except Exception as e:
# 生产环境中务必捕获数据库异常,防止连接泄漏或暴露敏感堆栈
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
# 将结果挂载到 request.state,方便后续调用
request.state.user = cur_user
request.state.is_authenticated = cur_user is not None
response = await call_next(request)
return response
二,测试效果

三,说明:
此处需要说明的一点:
在 FastAPI 的中间件中可以直接主动抛出(
raise)异常,但绝对不能直接使用 raise HTTPException()。如果你在中间件中直接
最终会演变成你上一问遇到的底层 Starlette 崩溃报错,或者直接对客户端返回模糊的
raise HTTPException,FastAPI 的全局异常处理器会无法正确捕捉它,最终会演变成你上一问遇到的底层 Starlette 崩溃报错,或者直接对客户端返回模糊的
Internal Server Error。解决方法:
直接返回
JSONResponse(最常用、最标准)在中间件的逻辑中,如果你发现请求不合法(例如黑名单、鉴权失败),最标准且优雅的做法是不使用
raise,而是直接 return 一个响应对象(如 JSONResponse)。这样请求会被立即拦截,不会进入后续的路由,也不会触发任何底层错误。
浙公网安备 33010602011771号