Spring/Spring Stomp

[Spring WebSockets] 2-1. STOMP

noahkim_ 2025. 5. 3. 18:56

1. STOMP란

  • 원래는 스크립트 언어(Ruby, Python, Perl 등)에서 엔터프라이즈 메시지 브로커와 통신할 수 있도록 만들어진 텍스트 기반 메시징 프로토콜.
  • TCP, WebSocket신뢰할 수 있는 양방향 스트리밍 프로토콜 위에서 동작 가능.
  • 텍스트 지향이지만, Payload(본문)텍스트 또는 바이너리 모두 지원 가능.

 

구성 요소

구성요소 설명
Message
헤더 + 페이로드를 가진 기본 메시지 객체
MessageHandler
메시지를 처리하는 계약 인터페이스
MessageChannel
메시지를 송신할 수 있는 인터페이스 (생산자와 소비자 분리)
SubscribableChannel
메시지를 구독할 수 있는 인터페이스
ExecutorSubscribableChannel
메시지를 비동기로 처리할 수 있도록 Executor를 사용하는 채널

 

주요 채널

채널명 방향 역할 동작
clientInboundChannel
클라이언트 서버
클라이언트 메시지 수신, 구독, 라우팅
CONNECT, SUBSCRIBE, SEND, @MessageMapping 연결
brokerChannel
서버  브로커
애플리케이션 메시지를 브로커로 전달
convertAndSend()
clientOutboundChannel 브로커  클라이언트
구독자에게 최종 메시지 전송
웹소켓 세션 전송

 

 

2. 메시지 흐름

내장 브로커

내장 브로커

  1. clientInboundChannel: 클라이언트가 보낸 STOMP 메시지를 서버가 수신 (CONNECT, SUBSCRIBE, SEND, DISCONNECTED)
  2. clientInboundChannel: 클라이언트가 보낸 SUBSCRIBE 요청을 받아 서버 내부에서 토픽별 세션 정보 관리
  3. clientInboundChannel: 클라이언트가 보낸 SEND 메시지를 @MessageMapping으로 전달
  4. brokerChannel: @MessageMapping 결과 또는 SimpMessagingTemplate 메시지를 SimpleBroker에 전달
  5. clientOutboundChannel: SimpleBroker가 구독중인 세션을 찾아 클라이언트에 메시지 전송

 

외장 브로커

외장 브로커

  1. clientInboundChannel: 클라이언트가 보낸 STOMP 메시지를 서버가 수신 (CONNECT, SUBSCRIBE, SEND, DISCONNECTED)
  2. clientInboundChannel: 클라이언트가 보낸 SUBSCRIBE 요청을 받아 세션/구독 정보 관리함.
  3. clientInboundChannel: 클라이언트가 보낸 SEND 메시지를 @MessageMapping으로 전달
  4. brokerChannel: @MessageMapping 결과 또는 SimpMessagingTemplate 메시지를 broker relay를 통해 외부 브로커로 publish함
  5. clientOutboundChannel: SimpleBroker가 구독중인 세션을 찾아 클라이언트에 메시지 전송

 

3. 이점

항목 설명 이점
@Controller를 통한 메시지 핸들링 @MessageMapping 메소드에 메시지 자동 분배
코드 구조가 모듈화되고 깔끔해짐
Spring Security를 통한 보안 설정 Destination별, 권한별 접근 제어  

 

예시) @Controller를 통한 메시지 핸들링 가능

더보기
@Controller
public class ChatController {

    // 클라이언트가 "/app/chat.send"로 메시지를 보내면 이 메소드가 호출됨
    @MessageMapping("/chat.send")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(ChatMessage message) {
        // 받은 메시지를 가공해서 다시 "/topic/public"으로 브로드캐스트
        return message;
    }
}

 

예시) Spring Security로 메시지 보안 설정 가능

더보기
@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(ChannelRegistration registration) {
        registration.interceptors(new AuthChannelInterceptorAdapter());
    }

    @Override
    protected boolean sameOriginDisabled() {
        return true; // CORS 정책 예외 (필요에 따라)
    }
}
  • STOMP 메시지가 들어오는 채널을 감시

 

@Configuration
@EnableWebSocketSecurity
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {

    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages
            .simpDestMatchers("/app/chat/**").authenticated()  // ① "/app/chat/**"로 가는 메시지는 인증 필요
            .simpDestMatchers("/admin/**").hasRole("ADMIN")    // ② "/admin/**"로 가는 메시지는 ADMIN 권한 필요
            .anyMessage().permitAll();                         // ③ 나머지는 모두 허용
    }
}
  • Destination별 인증/인가

 

4. 핸들링 어노테이션

어노테이션 역할 비고
@MessageMapping 클라이언트의 SEND 메시지를 특정 메서드로 연결
HTTP의 @RequestMapping와 유사
@SendTo 메서드 결과를 특정 topic으로 브로드캐스팅
구독중인 모든 사용자에게 전송
@SendToUser 메서드 결과를 특정 사용자에게 전송 /user/로 라우팅됨
@SubscribeMapping 클라이언트의 SUBSCRIBE 메시지를 특정 메서드와 연결
구독 직후 초기 데이터
@MessageExceptionHandler @MessageMapping 메서드 실행 중 예외 처리
사용자별 에러 전송 가능

 

예제) @MessageMapping

더보기
@MessageMapping("/chat")
public void handleChatMessage(Message message) {
    // 클라이언트가 /app/chat 으로 보내는 메시지를 처리
}

 

예제) @SendTo

더보기
@MessageMapping("/chat")
@SendTo("/topic/messages")
public Message send(Message message) {
    return message;
}

 

예제) @SendToUser

더보기
@MessageMapping("/private-message")
@SendToUser("/queue/reply")
public Message sendPrivate(Message message) {
    return message;
}

 

예제) @SubscribeMapping

더보기
@SubscribeMapping("/initial-data")
public InitialData sendInitialData() {
    return new InitialData();
}

 

예제) @MessageExceptionHandler

더보기
@MessageExceptionHandler
@SendToUser("/queue/errors")
public String handleException(Throwable exception) {
    return exception.getMessage();
}

 

출처