프로젝트: Snowpeak Game 스택: Spring WebSocket STOMP, Simple Broker, SockJS 한 줄 요약: 늦게 들어온 플레이어에게 "기존 플레이어 목록"을 보냈지만 아무 데도 도착하지 않았습니다. 인증(Principal)이 없는 환경에서
convertAndSendToUser가 동작하는 조건을 확인해 고쳤고, 토픽 하나로 브로드캐스트하던 개인 메시지의 보안 문제도 함께 정리했습니다.
새 플레이어가 들어오면, 그 사람에게만 현재 방에 있는 플레이어 목록을 보내야 합니다. 여러 번의 시도가 있었습니다.
| 시점 | 방법 | 결과 |
|---|---|---|
9670dfa |
convertAndSendToUser(sessionId, "/queue/players", ps)를 플레이어 수만큼 반복 |
도착하지 않음 |
957ab29 |
convertAndSend("/topic/private/" + nickname, syncMessage) |
도착은 하지만 누구나 구독 가능 |
| 현재 | 세션 ID 헤더를 붙인 convertAndSendToUser • /user/queue/sync |
본인 세션에만 도착 (테스트로 검증) |
convertAndSendToUser의 "user"는 기본적으로 로그인 사용자다convertAndSendToUser(user, "/queue/x", payload)는 내부에서 목적지를 /user/{user}/queue/x로 만듭니다. 그리고 UserDestinationMessageHandler가 이 user 이름에 연결된 세션들을 찾아 /queue/x-user{sessionId}로 바꿔 전달합니다.
Principal(인증된 사용자) 기준으로 관리됩니다.null입니다. 그래서 sessionId를 user 이름 자리에 넣어도 연결된 세션을 찾지 못해 메시지가 버려집니다. 오류 로그도 남지 않아 원인을 찾기 어려웠습니다./queue prefix도 등록돼 있지 않았습니다(enableSimpleBroker("/topic")만 있음)./topic/private/{nickname}은 "비공개"가 아니다957ab29에서는 1:1 전송을 포기하고 닉네임별 토픽으로 보냈습니다. 동작은 했지만 다음 문제가 있습니다.
Spring은 메시지 헤더에 세션 ID가 있고, user 이름이 그 세션 ID와 같으면 Principal 없이도 해당 세션으로 전달합니다.
// GameService
private void sendToSession(String sessionId, String destination, ServerMessage message) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headers.setSessionId(sessionId);
headers.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(sessionId, destination, message, headers.getMessageHeaders());
}
// WebSocketConfig
config.enableSimpleBroker("/topic", "/queue");