diff --git a/backend/src/main/java/com/danyang/match/platform/chat/config/WebSocketConfig.java b/backend/src/main/java/com/danyang/match/platform/chat/config/WebSocketConfig.java
index d4407c6..e742dcd 100644
--- a/backend/src/main/java/com/danyang/match/platform/chat/config/WebSocketConfig.java
+++ b/backend/src/main/java/com/danyang/match/platform/chat/config/WebSocketConfig.java
@@ -1,5 +1,6 @@
package com.danyang.match.platform.chat.config;
+import com.danyang.match.platform.chat.websocket.ChatHandshakeInterceptor;
import com.danyang.match.platform.chat.websocket.ChatWebSocketHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
@@ -10,15 +11,28 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
/**
* WebSocket 配置 — 实时消息推送
*
- * 端点: /ws/chat?token={jwt}
- * 协议: JSON (type + payload)
- * - type="message" → 新消息推送
- * - type="typing" → 对方正在输入
- * - type="read" → 已读回执
- * - type="match" → 新 Match 通知
- * - type="unmatch" → Match 解除
+ *
端点: {@code /ws/chat?token={jwt}}(原生 WebSocket,非 SockJS)
*
- * 鉴权: 握手时 URL 带 JWT token(与 REST 同密钥)
+ *
协议:事件信封对齐《REST API 接口详细设计规范》第 125 章,形如
+ *
{ "event": "message.new", "data": { ... } }
+ * 事件名沿用规范第 124 章清单:
+ *
+ * - {@code message.new} — 新消息
+ * - {@code message.read} — 已读回执
+ * - {@code conversation.closed} — 会话关闭(Unmatch / Block)
+ * - {@code match.created} — 新 Match
+ * - {@code notification.new} — 通知
+ * - {@code account.status_changed} — 账号状态变更
+ *
+ *
+ * 鉴权:握手阶段由 {@link ChatHandshakeInterceptor} 校验 {@code token} 并把 userId
+ * 绑定到连接(规范第 126 章)。
+ *
+ *
关于 SockJS:此前本配置调用了 {@code .withSockJS()}。SockJS 会把端点改写为
+ * {@code /ws/chat/**} 的 SockJS 协议族,原生 WebSocket 客户端(小程序
+ * {@code wx.connectSocket}、规范第 124 章给的 {@code /wss/chat})无法直接连接,
+ * 与类注释自述的 {@code /ws/chat?token=} 原生契约自相矛盾。
+ * 本项目两端都是原生 WebSocket 客户端,故移除 SockJS。
*/
@Configuration
@EnableWebSocket
@@ -26,11 +40,14 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
public class WebSocketConfig implements WebSocketConfigurer {
private final ChatWebSocketHandler chatHandler;
+ private final ChatHandshakeInterceptor chatHandshakeInterceptor;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(chatHandler, "/ws/chat")
- .setAllowedOriginPatterns("*")
- .withSockJS(); // SockJS fallback for miniapp 兼容
+ .addInterceptors(chatHandshakeInterceptor)
+ // 小程序 / 原生客户端握手不带 Origin,需允许任意来源;
+ // 真正的访问控制由握手 token 鉴权承担,不以 Origin 作为安全边界。
+ .setAllowedOriginPatterns("*");
}
}
diff --git a/backend/src/main/java/com/danyang/match/platform/chat/controller/ConversationController.java b/backend/src/main/java/com/danyang/match/platform/chat/controller/ConversationController.java
index d067470..59b9f20 100644
--- a/backend/src/main/java/com/danyang/match/platform/chat/controller/ConversationController.java
+++ b/backend/src/main/java/com/danyang/match/platform/chat/controller/ConversationController.java
@@ -92,6 +92,8 @@ public class ConversationController {
public ApiResponse detail(@PathVariable String id,
@AuthenticationPrincipal UserPrincipal principal) {
if (principal == null) return ApiResponse.unauthorized();
+ // 对象级鉴权:非会话成员一律 404 —— 否则可凭 conversationId 读到对方昵称、最后一条消息
+ if (!chatService.isMember(id, principal.id())) return ApiResponse.notFound();
ConversationDTO dto = chatService.getConversation(principal.id(), id);
if (dto == null) return ApiResponse.notFound();
return ApiResponse.ok(dto);
@@ -105,6 +107,8 @@ public class ConversationController {
public ApiResponse