diff --git a/src/main/java/com/webgame/webgamebackend/common/config/SaTokenConfigure.java b/src/main/java/com/webgame/webgamebackend/common/config/SaTokenConfigure.java index dd3a8f3..45ab45a 100644 --- a/src/main/java/com/webgame/webgamebackend/common/config/SaTokenConfigure.java +++ b/src/main/java/com/webgame/webgamebackend/common/config/SaTokenConfigure.java @@ -31,6 +31,7 @@ public class SaTokenConfigure implements WebMvcConfigurer { .notMatch("/v3/api-docs/**") .notMatch("/webjars/**") .notMatch("/error") + .notMatch("/sse/**") .check(r -> StpUtil.checkLogin()); })).addPathPatterns("/**") .excludePathPatterns("/error"); diff --git a/src/main/java/com/webgame/webgamebackend/sse/SseController.java b/src/main/java/com/webgame/webgamebackend/sse/SseController.java index 55c5748..22a4f5d 100644 --- a/src/main/java/com/webgame/webgamebackend/sse/SseController.java +++ b/src/main/java/com/webgame/webgamebackend/sse/SseController.java @@ -1,6 +1,7 @@ package com.webgame.webgamebackend.sse; import cn.dev33.satoken.stp.StpUtil; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; @@ -31,8 +32,26 @@ public class SseController { @GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter subscribe( - @RequestHeader(value = "Last-Event-ID", required = false) String lastEventId) { - String userId = StpUtil.getLoginIdAsString(); + @RequestHeader(value = "Last-Event-ID", required = false) String lastEventId, + HttpServletResponse response) throws IOException { + + // SSE 路径已排除在 SaInterceptor 之外,此处手动校验登录态 + String token = StpUtil.getTokenValue(); + if (token == null || token.isBlank()) { + log.warn("[SSE] 缺少认证 token"); + return writeAuthError(response, HttpServletResponse.SC_UNAUTHORIZED, "缺少认证信息,请先登录"); + } + + Object loginId = StpUtil.getLoginIdByToken(token); + if (loginId == null) { + log.warn("[SSE] token 无效或已过期"); + return writeAuthError(response, HttpServletResponse.SC_UNAUTHORIZED, "登录已过期,请重新登录"); + } + + // 设置 Sa-Token 上下文,后续 StpUtil.getLoginIdAsString() 可用 + StpUtil.setTokenValue(token, -1); + + String userId = loginId.toString(); SseEmitter emitter = new SseEmitter(SSE_TIMEOUT_MS); String connectionId = connectionManager.register(userId, emitter); @@ -56,4 +75,21 @@ public class SseController { userId, connectionId, connectionManager.getOnlineCount()); return emitter; } + + /** + * 向 SSE 客户端返回认证错误 + * + * 直接写入 HttpServletResponse 返回 401 + SSE error 事件, + * 前端 SseClient.onopen 检测到 response.status === 401 后触发 onUnauthorized, + * 自动刷新 token 并重连。 + * + * @return null,通知 Spring MVC 响应已处理完毕,无需 MessageConverter + */ + private SseEmitter writeAuthError(HttpServletResponse response, int status, String message) throws IOException { + response.setStatus(status); + response.setContentType(MediaType.TEXT_EVENT_STREAM_VALUE); + response.getWriter().write("event: error\ndata: {\"code\":" + status + ",\"message\":\"" + message + "\"}\n\n"); + response.getWriter().flush(); + return null; + } }