SaInterceptor 在 ASYNC 收尾派发上重复鉴权,token 在长流期间失效会中止已 committed 的流式响应
Author: bennameCreated Aug 18, 2026Updated Aug 18, 2026
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 通用模块合适。
Source: dromara/Sa-Token