Skip to content

fix(webmvc): SaInterceptor 在 ASYNC 收尾派发上不再重复鉴权 - #978

Open
Rainmemery wants to merge 1 commit into
dromara:devfrom
Rainmemery:fix/async-dispatch-double-auth
Open

Rainmemery wants to merge 1 commit into
dromara:devfrom
Rainmemery:fix/async-dispatch-double-auth

Conversation

@Rainmemery

Copy link
Copy Markdown

Closes #967

问题

1.46.0(7c986649)将上下文过滤器注册为 REQUEST+ASYNC 后(#963 的修复),Spring MVC 异步请求的收尾派发会再次执行 DispatcherServlet#applyPreHandleSaInterceptor 于是对同一请求第二次执行注解鉴权。此时响应通常已经 committed(SSE / StreamingResponseBody 长流场景,AI 流式输出可以持续几分钟):

  • token 在流进行期间过期 / 注销 / 被顶下线;
  • 收尾派发上 StpUtil.checkLogin 抛出 NotLoginException
  • 响应已提交,异常无法转为错误响应,容器只能中止连接:客户端丢掉整条流,Nginx 记录 upstream prematurely closed connection

这次重复鉴权并不能提供额外安全性——数据在首段鉴权通过后就已经开始下发了。

修复

SaInterceptor.preHandlesa-token-spring-boot-webmvc-v3v4-common)对满足以下条件的派发直接放行:

request.getDispatcherType() == DispatcherType.ASYNC
    && WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()
  • 首次 REQUEST 派发已完成鉴权,收尾派发仅负责完成异步结果渲染;
  • hasConcurrentResult()false 的跨资源 ASYNC 派发(如 AsyncContext.dispatch("/other/path"),目标 handler 的鉴权注解必须执行)不受影响,避免放宽条件导致越权;
  • 与 Spring Security 在 ASYNC 派发上校验「首段捕获的 SecurityContext 快照」的语义一致:中途注销不会阻断同一请求的收尾渲染。

测试

sa-token-integration-boot4 新增 AsyncDispatchAuthTest@SaCheckLogin + DeferredResult 异步端点,MockMvc asyncDispatch 走真实的 ASYNC 收尾派发路径):

  1. asyncCompletionDispatch_shouldNotReAuthAfterTokenLogout — 流进行期间 StpUtil.logoutByTokenValue(token) 后,收尾派发正常完成响应(修复前该用例失败,即 issue 中的连接中止现象);
  2. asyncCompletionDispatch_shouldPassWithValidToken — token 有效时收尾派发正常;
  3. initialDispatch_shouldStillRequireLogin — 初始 REQUEST 派发鉴权不受影响,未登录仍返回 401。

验证过程(Ubuntu 22.04 + JDK 17 + Maven):

  • 基线(原版 SaInterceptor,reactor 构建):Tests run: 3, Failures: 1 — 用例 1 失败,复现 issue 场景;
  • 修复后:Tests run: 3, Failures: 0
  • 模块全量回归:sa-token-integration-boot4 Tests run: 19, Failures: 0, Errors: 0

1.46.0 将上下文过滤器注册为 REQUEST+ASYNC 后(dromara#963),Spring MVC 异步请求的收尾派发会再次执行 applyPreHandle,SaInterceptor 于是对同一请求第二次执行注解鉴权。此时响应通常已提交(SSE/StreamingResponseBody 长流),token 失效抛出的 NotLoginException 无法转为错误响应,容器只能中止连接,客户端丢失整条流。

preHandle 现在对 DispatcherType.ASYNC 且 WebAsyncManager 已持有并发结果的派发直接放行;跨资源 ASYNC 派发(如 AsyncContext.dispatch("/other"),无并发结果)仍正常鉴权,避免越权。

Closes dromara#967
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.

SaInterceptor 在 ASYNC 收尾派发上重复鉴权,token 在长流期间失效会中止已 committed 的流式响应

1 participant