修复WebSocket身份认证问题

- 添加WebSocketAuthInterceptor处理token认证
- 修改WebSocket连接逻辑,支持token传递
- 统一用户身份识别,确保登录用户使用USER类型
- 修复前端环境变量配置,统一WebSocket URL
- 添加Token测试页面用于验证功能
- 更新聊天消息处理逻辑,正确识别用户身份

解决了登录用户发送消息时同时保存GUEST和USER两种类型数据的问题
This commit is contained in:
2025-07-24 17:51:38 +08:00
parent 6560e66959
commit 847f5126cf
30 changed files with 1447 additions and 216 deletions
@@ -0,0 +1,124 @@
package com.emotion.interceptor;
import com.emotion.service.AuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.security.Principal;
import java.util.Collections;
/**
* WebSocket认证拦截器
* 用于在WebSocket连接时验证用户身份
*
* @author emotion-museum
* @date 2025-07-24
*/
@Slf4j
@Component
public class WebSocketAuthInterceptor implements ChannelInterceptor {
@Autowired
private AuthService authService;
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) {
// 处理WebSocket连接时的认证
handleAuthentication(accessor);
}
return message;
}
/**
* 处理WebSocket连接认证
*/
private void handleAuthentication(StompHeaderAccessor accessor) {
try {
// 从连接头中获取token
String authHeader = accessor.getFirstNativeHeader("Authorization");
String userId = accessor.getFirstNativeHeader("X-User-Id");
log.info("WebSocket连接认证: authHeader={}, userId={}",
authHeader != null ? "Bearer ***" : null, userId);
if (StringUtils.hasText(authHeader) && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// 验证token
if (authService.validateToken(token)) {
String tokenUserId = authService.getUserIdFromToken(token);
String username = authService.getUsernameFromToken(token);
log.info("WebSocket token验证成功: userId={}, username={}", tokenUserId, username);
// 创建认证对象
Authentication authentication = new UsernamePasswordAuthenticationToken(
tokenUserId,
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);
// 设置用户认证信息
accessor.setUser(authentication);
// 设置会话属性
accessor.getSessionAttributes().put("userId", tokenUserId);
accessor.getSessionAttributes().put("username", username);
accessor.getSessionAttributes().put("authenticated", true);
} else {
log.warn("WebSocket token验证失败: token无效");
// token无效,但不阻止连接,作为访客处理
handleGuestUser(accessor, userId);
}
} else {
log.info("WebSocket连接无token,作为访客处理: userId={}", userId);
// 无token,作为访客处理
handleGuestUser(accessor, userId);
}
} catch (Exception e) {
log.error("WebSocket认证处理失败", e);
// 认证失败,作为访客处理
handleGuestUser(accessor, accessor.getFirstNativeHeader("X-User-Id"));
}
}
/**
* 处理访客用户
*/
private void handleGuestUser(StompHeaderAccessor accessor, String userId) {
String guestId = StringUtils.hasText(userId) ? userId : "guest_" + System.currentTimeMillis();
log.info("设置访客用户: guestId={}", guestId);
// 创建访客认证对象
Authentication guestAuth = new UsernamePasswordAuthenticationToken(
guestId,
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_GUEST"))
);
accessor.setUser(guestAuth);
// 设置会话属性
accessor.getSessionAttributes().put("userId", guestId);
accessor.getSessionAttributes().put("username", guestId);
accessor.getSessionAttributes().put("authenticated", false);
}
}