Skip to content

Add underlying security config for JWT#1214

Merged
wu-clan merged 1 commit into
masterfrom
udpate-jwt-middleware
Jul 2, 2026
Merged

Add underlying security config for JWT#1214
wu-clan merged 1 commit into
masterfrom
udpate-jwt-middleware

Conversation

@wu-clan

@wu-clan wu-clan commented Jul 1, 2026

Copy link
Copy Markdown
Member

close #1213

@wu-clan wu-clan merged commit efec330 into master Jul 2, 2026
5 checks passed
@wu-clan wu-clan deleted the udpate-jwt-middleware branch July 2, 2026 06:42
@wuyuemushi

Copy link
Copy Markdown
Contributor

这个补丁会产生新的问题:

JwtAuthMiddleware.authenticate 中的 token 验证失败逻辑从抛异常改为返回 None

# 修复前
try:
    user = await jwt_authentication(token)
except TokenError as exc:
    raise AuthenticationError(code=exc.code, msg=exc.detail, headers=exc.headers)

# 修复后
try:
    user = await jwt_authentication(token)
except Exception:
    return None

目的

解决公开接口(如 GET /agreements)被误拦截的问题——前端传了过期 token 时,中间件不再拦截,让请求正常到达路由处理函数。

引发的新问题

修复后,需认证的接口传了过期 token 时,出现 500 内部错误:

File "backend/app/admin/api/v1/sys/user.py", line 27, in get_current_user
    data = request.user.model_dump()
           ^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'UnauthenticatedUser' object has no attribute 'model_dump'

原因分析

认证职责分散在两层,但只在一层做了真正的工作:

层级 组件 实际行为
中间件 JwtAuthMiddleware 验证 JWT → 注入 user → 不再拦截
路由层 DependsJwtAuth = Depends(HTTPBearer()) 只检查 token 字符串是否存在,不验证有效性
调用链:
需认证接口 + 过期 token
  → 中间件: jwt_authentication 失败 → return None → request.user = UnauthenticatedUser → 放行
  → DependsJwtAuth: HTTPBearer 检查 token 字符串存在 → 放行
  → 路由函数: request.user.model_dump() → 💥 UnauthenticatedUser 没有 model_dump 方法

结果: 认证从"显式 401 拒绝"变成了"隐式 500 错误",更难排查。

根因

DependsJwtAuth 只是个空壳(HTTPBearer() 只检查 token 是否存在),真正的 JWT 验证一直由中间件默默兜底。中间件退回去不做拦截后,路由层没有能力验证 token 有效性,导致无效 token 静默通过,最终在业务代码中崩。

建议

两个改动配套进行,缺一不可:

  1. 中间件只解析不拦截 —— 解决公开接口误拦截
  2. DependsJwtAuth 改为验证 user 有效性—— 确保需认证接口正确拒绝无效 token
# backend/common/security/jwt.py
async def verify_jwt(request: Request) -> str:
    if isinstance(request.user, UnauthenticatedUser):
        raise errors.TokenError
    auth = request.headers.get('Authorization')
    if not auth:
        raise errors.TokenError
    _, token = get_authorization_scheme_param(auth)
    return token

DependsJwtAuth = Depends(verify_jwt)

这样认证职责完整收归路由层,中间件只做解析注入,两层职责单一,不再互相依赖兜底。

@wu-clan

wu-clan commented Jul 2, 2026

Copy link
Copy Markdown
Member Author

感谢指出,这个问题确实存在

#1214 默认行为没有改变,TOKEN_REQUEST_UNDERLYING_SECURITY 默认仍是 True,所以默认情况下过期 Token 仍会在中间件层返回 401

但当该配置被设置为 False 时,你说的边界问题是成立的:中间件不再拦截无效 Token,而 DependsJwtAuth = Depends(HTTPBearer()) 只检查 Token 是否存在,无法保证 request.user 已认证,最终可能导致需认证接口出现 500

这个配置需要和路由层认证校验配套调整,我会后续把 DependsJwtAuth 改为校验 request.user 是否为已认证用户,避免无效 Token 在需认证接口中静默通过

感谢反馈

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

全局 JWT 中间件与路由层认证策略不一致,导致公开接口可能被误拦截

2 participants