프로젝트: Snowpeak Game 스택: Spring WebSocket STOMP, Simple Broker, SockJS 한 줄 요약: 늦게 들어온 플레이어에게 "기존 플레이어 목록"을 보냈지만 아무 데도 도착하지 않았습니다. 인증(Principal)이 없는 환경에서 convertAndSendToUser가 동작하는 조건을 확인해 고쳤고, 토픽 하나로 브로드캐스트하던 개인 메시지의 보안 문제도 함께 정리했습니다.


1. 문제

새 플레이어가 들어오면, 그 사람에게만 현재 방에 있는 플레이어 목록을 보내야 합니다. 여러 번의 시도가 있었습니다.

시점 방법 결과
9670dfa convertAndSendToUser(sessionId, "/queue/players", ps)를 플레이어 수만큼 반복 도착하지 않음
957ab29 convertAndSend("/topic/private/" + nickname, syncMessage) 도착은 하지만 누구나 구독 가능
현재 세션 ID 헤더를 붙인 convertAndSendToUser • /user/queue/sync 본인 세션에만 도착 (테스트로 검증)

2. 원인 1: convertAndSendToUser의 "user"는 기본적으로 로그인 사용자다

convertAndSendToUser(user, "/queue/x", payload)는 내부에서 목적지를 /user/{user}/queue/x로 만듭니다. 그리고 UserDestinationMessageHandler가 이 user 이름에 연결된 세션들을 찾아 /queue/x-user{sessionId}로 바꿔 전달합니다.

3. 원인 2: /topic/private/{nickname}은 "비공개"가 아니다

957ab29에서는 1:1 전송을 포기하고 닉네임별 토픽으로 보냈습니다. 동작은 했지만 다음 문제가 있습니다.

4. 해결

4-1. 세션 ID를 헤더에 넣어 보낸다

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");