Skip to content

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

Open

Description

@benname

1.46.0(7c986649)把上下文过滤器注册成 REQUEST+ASYNC 之后,#963 的上下文异常已解决。但同一场景还有另一半:

Spring MVC 的异步请求在响应写完后会经历一次 Servlet ASYNC 收尾派发,DispatcherServlet.doDispatch 在这次派发里再次执行 applyPreHandle,SaInterceptor 于是对同一请求第二次执行注解鉴权。此时响应通常已经 committed:

  • SSE / StreamingResponseBody 长连接持续几分钟很常见(比如 AI 流式输出);
  • token 在流进行期间过期,或被注销/顶下线;
  • 收尾派发上 StpUtil.checkLogin 抛 NotLoginException;
  • 响应已 committed,异常无法转成错误响应,容器只能中止连接:客户端丢整条流,Nginx 记 upstream prematurely closed connection。

复现条件:@SaCheckLogin + SseEmitter/StreamingResponseBody,流的持续时间跨过 token 有效期;或者流进行中把该 token 注销。

这次重复鉴权拦不住任何东西——数据在首段鉴权通过后就已经开始下发了。建议 SaInterceptor.preHandle 跳过异步收尾派发:

if (request.getDispatcherType() == DispatcherType.ASYNC
        && WebAsyncUtils.getAsyncManager(request).hasConcurrentResult()) {
    return true;
}

判断条件不能放宽成 dispatcherType != REQUEST:AsyncContext.dispatch("/other/path") 这类派发到另一个资源的场景,目标 handler 的鉴权注解必须执行,一律跳过会越权。

Spring Security 在 ASYNC 派发上也执行授权过滤器,但它校验的是首段捕获、经 request attribute 恢复的 SecurityContext——等效于鉴权快照,中途注销不会阻断同一请求的收尾渲染。

WebAsyncUtils 在 spring-web 里,这段判断放在 webmvc 相关模块比放在 servlet 通用模块合适。

Activity

  1. click33 commented on Aug 18, 2026

    @click33
    Collaborator

    让我歇两天 🤒

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions