diff --git a/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/configuration/SaTokenConfigure.java b/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/configuration/SaTokenConfigure.java index dc54b4856..9253cc54b 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/configuration/SaTokenConfigure.java +++ b/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/configuration/SaTokenConfigure.java @@ -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("/**"); } } diff --git a/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/handler/SaExceptionHandler.java b/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/handler/SaExceptionHandler.java index 18313ec0e..50dc39607 100644 --- a/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/handler/SaExceptionHandler.java +++ b/daxpay-platform/daxpay-platform-capability/capability-auth/src/main/java/cn/daxpay/open/platform/capability/auth/handler/SaExceptionHandler.java @@ -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 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 result = Res.response(CommonCode.FAIL_CODE, ex.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .contentType(MediaType.APPLICATION_JSON) + .body(result); } }