mirror of
https://gitee.com/dromara/dax-pay
synced 2026-08-12 23:45:39 +08:00
fix(auth): 修复 SSE 异步连接 ASYNC 派发时 SaToken 上下文未初始化
SSE 端点(/notify/user/sse/connect)连接完成/超时/异常时, Servlet 容器做 ASYNC 重派发, SaToken 上下文过滤器(OncePerRequestFilter, 仅 REQUEST 派发)不重新执行, 再次进入 SaInterceptor 抛 SaTokenContextException; 异常处理器对已锁定 text/event-stream 的响应写 JSON 又二次抛 HttpMessageNotWritableException. - SaTokenConfigure: 包装 SaInterceptor, DispatcherType.ASYNC 时直接放行 - SaExceptionHandler: SaTokenException 命中 text/event-stream 时不写 body, 正常分支显式 JSON
This commit is contained in:
@@ -4,11 +4,15 @@ import cn.daxpay.open.platform.common.config.properties.PlatformStarterPropertie
|
||||
import cn.daxpay.open.platform.capability.auth.handler.SaRouteHandler;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import jakarta.servlet.DispatcherType;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@@ -58,7 +62,17 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
.check(saRouteHandler.check(handler))
|
||||
);
|
||||
// 注册路由拦截器,自定义验证规则
|
||||
registry.addInterceptor(saInterceptor)
|
||||
.addPathPatterns("/**");
|
||||
// 包装一层: SSE 等异步连接(SseEmitter)在完成/超时/异常时, Servlet 容器会做 ASYNC 重派发,
|
||||
// 此时 SaToken 上下文过滤器(OncePerRequestFilter, 仅 REQUEST 派发)不会重新执行, ThreadLocal 上下文为空,
|
||||
// 若再次进入 SaInterceptor 会抛 SaTokenContextException. 鉴权已在初始 REQUEST 派发完成, ASYNC 派发直接放行.
|
||||
registry.addInterceptor(new HandlerInterceptor() {
|
||||
@Override
|
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
|
||||
if (request.getDispatcherType() == DispatcherType.ASYNC) {
|
||||
return true;
|
||||
}
|
||||
return saInterceptor.preHandle(request, response, handler);
|
||||
}
|
||||
}).addPathPatterns("/**");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import cn.daxpay.open.platform.core.rest.result.Result;
|
||||
import cn.daxpay.open.platform.capability.auth.exception.NotLoginException;
|
||||
import cn.daxpay.open.platform.capability.auth.exception.RouterCheckException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
@@ -39,10 +41,21 @@ public class SaExceptionHandler {
|
||||
}
|
||||
|
||||
/// sa鉴权业务异常
|
||||
///
|
||||
/// 注意: SSE 等异步端点产生的事件流响应 Content-Type 已锁定为 text/event-stream,
|
||||
/// 无 JSON 消息转换器, 此时返回 Result 会抛 HttpMessageNotWritableException, 故仅返回无 body 的状态码.
|
||||
@ExceptionHandler(SaTokenException.class)
|
||||
public Result<Void> handleBusinessException(SaTokenException ex) {
|
||||
public ResponseEntity<?> handleBusinessException(SaTokenException ex, HttpServletResponse response) {
|
||||
log.info(ex.getMessage(), ex);
|
||||
return Res.response(CommonCode.FAIL_CODE, ex.getMessage());
|
||||
String contentType = response.getContentType();
|
||||
// SSE 事件流响应: 无 JSON 转换器, 不写 body, 避免二次异常
|
||||
if (contentType != null && contentType.contains(MediaType.TEXT_EVENT_STREAM_VALUE)) {
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
Result<Void> result = Res.response(CommonCode.FAIL_CODE, ex.getMessage());
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.body(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user