FastAPI如何获取路由原始路径?实现登录验证中间件
FastAPI 实现全局登录验证(login_required)的生产级方案
针对你提出的需求,生产环境中优先推荐依赖系统或端点标记式中间件方案,完全解决动态路径匹配问题,同时支持精准控制豁免路由。以下是具体实现:
方案1:全局依赖+路由级豁免(最推荐,生产常用)
FastAPI 的依赖系统原生支持全局/路由级控制,比中间件更贴合框架设计,无需处理路径匹配逻辑。
步骤1:定义登录验证依赖
from fastapi import Depends, HTTPException, status, Request async def login_required(request: Request): """验证请求是否已登录""" if not hasattr(request.state, "user") or request.state.user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录", headers={"WWW-Authenticate": "Bearer"}, )
步骤2:给应用添加全局依赖
from fastapi import FastAPI # 全局应用默认启用登录验证 app = FastAPI(dependencies=[Depends(login_required)])
步骤3:豁免无需验证的路由
在不需要登录的路由上,用dependencies=[]覆盖全局依赖,动态路由直接生效,无需额外处理:
# 静态公开路由 @app.get("/public", dependencies=[]) async def public_route(): return {"message": "无需登录即可访问"} # 动态路径路由(自动适配,不用写匹配规则) @app.get("/posts/all/{page:int}", dependencies=[]) async def get_posts(page: int): return {"page": page, "posts": []}
方案2:端点标记式中间件(适合坚持用中间件的场景)
通过给豁免路由的端点函数打标记,中间件直接判断标记,彻底避开路径匹配问题。
步骤1:定义公开路由标记装饰器
from functools import wraps def public_route(func): """标记该路由为公开,无需登录验证""" @wraps(func) def wrapper(*args, **kwargs): return func(*args, **kwargs) wrapper.is_public = True return wrapper
步骤2:给豁免路由添加标记
@app.get("/login") @public_route async def login(): return {"message": "登录页面"} @app.get("/posts/all/{page:int}") @public_route async def get_posts(page: int): return {"page": page}
步骤3:编写中间件判断标记
from fastapi import Request, HTTPException, status from starlette.middleware.base import BaseHTTPMiddleware class AuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): # 获取当前请求对应的端点函数 endpoint = request.scope.get("endpoint") # 判断端点是否有公开标记 if endpoint and hasattr(endpoint, "is_public") and endpoint.is_public: return await call_next(request) # 执行登录验证 if not hasattr(request.state, "user") or request.state.user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录", headers={"WWW-Authenticate": "Bearer"}, ) response = await call_next(request) return response # 注册中间件 app.add_middleware(AuthMiddleware)
方案3:改进型路径匹配(不推荐,维护成本高)
如果一定要用路径列表方式,可借助Starlette的路由匹配能力处理动态路径:
from starlette.routing import Match from fastapi import Request, HTTPException, status from starlette.middleware.base import BaseHTTPMiddleware class AuthMiddleware(BaseHTTPMiddleware): # 定义豁免路由模板(支持动态路径) PUBLIC_ROUTES = ["/public", "/posts/all/{page:int}", "/login"] async def dispatch(self, request: Request, call_next): # 遍历应用路由,匹配当前请求 for route in request.app.routes: match, _ = route.matches(request.scope) if match == Match.FULL and route.path in self.PUBLIC_ROUTES: return await call_next(request) # 登录验证逻辑 if not hasattr(request.state, "user") or request.state.user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录", headers={"WWW-Authenticate": "Bearer"}, ) response = await call_next(request) return response
方案4:装饰器实现(备选)
适合路由数量少、不需要全局控制的场景:
def login_required(func): @wraps(func) async def wrapper(request: Request, *args, **kwargs): if not hasattr(request.state, "user") or request.state.user is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="请先登录", headers={"WWW-Authenticate": "Bearer"}, ) return await func(request, *args, **kwargs) return wrapper # 需要验证的路由添加装饰器 @app.get("/protected") @login_required async def protected_route(request: Request): return {"user": request.state.user} # 无需验证的路由直接定义 @app.get("/posts/all/{page:int}") async def get_posts(page: int): return {"page": page}
总结
- 生产环境优先选方案1:符合FastAPI设计哲学,代码简洁,维护成本极低,完美解决动态路径问题。
- 若必须用中间件,选方案2:通过端点函数标记判断,避免路径匹配的繁琐,逻辑清晰可靠。
- 关于「通过请求判断调用的端点函数」:方案2中
request.scope.get("endpoint")可直接获取当前请求对应的端点函数,生产环境中该方式稳定可用。
内容的提问来源于stack exchange,提问作者John Dow
相关产品推荐
相关产品推荐

