@PathVariable, @RequestBody 사용 시 값이 바인딩되지 않는 문제문제
REST 컨트롤러에서 사용하던 @PathVariable, @RequestBody를 WebSocket 컨트롤러에서 그대로 사용하면 값이 정상적으로 바인딩되지 않음.
원인
@PathVariable, @RequestBody는 HTTP 요청 처리 전용 어노테이션. WebSocket STOMP는 HTTP와 다른 프로토콜이라 동작하지 않음.
| HTTP | WebSocket STOMP | 역할 |
|---|---|---|
@PathVariable |
@DestinationVariable |
destination 경로의 변수 추출 |
@RequestBody |
@Payload |
메시지 본문 역직렬화 |
해결
STOMP 전용 어노테이션으로 교체. @DestinationVariable은 /pub/chat/{roomId} 같은 STOMP destination 경로에서 변수를 추출하고, @Payload는 STOMP 프레임의 body를 Java 객체로 역직렬화. @Payload에 @Valid를 함께 사용하면 Bean Validation도 적용 가능.
@MessageMapping("/chat/{roomId}")
public void sendMessage(
@DestinationVariable Long roomId,
@Payload @Valid UserChatMessageRequest request,
Principal principal
) { ... }
@AuthenticationPrincipal이 WebSocket 컨텍스트에서 동작하지 않는 문제문제
REST 컨트롤러에서 사용하던 @AuthenticationPrincipal CustomUserDetails userDetails 방식을 WebSocket 컨트롤러에서 그대로 적용했으나 userId가 계속 null로 찍힘.
원인
@AuthenticationPrincipal은 Spring Security의 SecurityContextHolder에서 인증 정보를 꺼내오는데, WebSocket 메시지 처리는 HTTP 요청 처리 스레드와 다른 컨텍스트에서 실행되어 SecurityContextHolder에 인증 정보가 없음.
해결
Principal 타입으로 직접 주입받아 UsernamePasswordAuthenticationToken으로 캐스팅해 CustomUserDetails를 추출.
// 변경 전
@AuthenticationPrincipal CustomUserDetails userDetails
// 변경 후
Principal principal
if (!(principal instanceof UsernamePasswordAuthenticationToken token)) {
return;
}
CustomUserDetails userDetails = (CustomUserDetails) token.getPrincipal();