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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 08:03:28