Stomp 구현

이상민·2025년 5월 22일
0

stomp 동작 흐름

사전 조건

  • 클라이언트 A, B, C는 이미 WebSocket을 통해 서버에 연결되어 있고,

  • 각각 room1이라는 topic(채팅방) 을 구독하고 있다고 가정.

0단계: 웹소켓 연결 및 구독

  • 클라이언트는 WebSocket으로 서버에 연결 (/ws)

  • 연결 후 특정 채널(topic)을 구독 (/sub/chat/room1)

  • connect : 웹소켓 연결하는 함수, 첫 번째 인자 {}는 headers (인증 토큰 등을 넣을 수 있다)

  • 두 번째 인자 (callback)은 연결 성공 시 실행될 콜백 함수

this.stompClient.connect({},() =>{
                    this.stompClient.subscribe(`/topic/1`,(message) =>{
                        this.messages.push(message.body);
                        this.scrollToBottom();
                    });
                    console.log("send message!!")
                }
            )


여기서 app은 publish를 의미한다

1단계: 메시지 전송 요청 (Client → Broker)

  • 클라이언트 A가 메시지를 입력하고 송신 버튼을 누름
  • 메시지는 @MessageMapping("/publish/{roomId}")
  sendMessage(){

            if (this.newMessage.trim()==="")return;
            this.stompClient.send(`/publish/1`,this.newMessage);
            this.newMessage = "";
        },

2단계: Broker가 메시지를 수신하고 해당 Topic에 발행

  • Spring의 SimpleMessageBroker는 메시지를 분석해 해당 room(room1)을 구독 중인 클라이언트를 파악

  • 해당 room(topic)에 메시지를 broadcast(발행) 함

3단계: 메시지 수신 (Broker → Clients)

  • room1을 구독 중이던 모든 클라이언트(B, C 포함)가 메시지를 수신함

  • 클라이언트 측 콜백 함수에서 메시지를 처리하여 UI에 출력함

stomp 장점

세션 정보를 직접 서버에서 관리할 필요없이 알아서 클라이언트별 구독 정보를 관리

STOMP 채팅 구현

StompConfig

@Configuration
@EnableWebSocketMessageBroker
public class StompWebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/connect") // 클라이언트가 WebSocket 연결을 시도할 URL
                .setAllowedOrigins("http://localhost:3000") // CORS 허용 도메인 설정
                .withSockJS(); // WebSocket을 지원하지 않는 브라우저도 fallback으로 HTTP 기반 통신 가능
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 클라이언트가 메시지를 보낼 때 사용하는 prefix 설정
        // 예: stompClient.send("/publish/roomId", payload)
        registry.setApplicationDestinationPrefixes("/publish"); 
        // → @MessageMapping("/chat") 메서드와 매핑됨

        // 클라이언트가 메시지를 구독할 때 사용하는 prefix 설정
        // 예: stompClient.subscribe("/topic/room1", callback)
        registry.enableSimpleBroker("/topic"); 
        // → 서버가 메시지를 이 prefix로 클라이언트에게 push함
    }
}
@Controller
public class ChatController {

    @MessageMapping("/{roomId}") // 클라이언트가 /publish/chat 으로 보낸 메시지 수신
    @SendTo("/topic/{roomId}")  // room1을 구독 중인 클라이언트에게 메시지 전파
    public ChatMessage sendMessage(ChatMessage message) {
        return message; // 그대로 반환하면 브로커가 전송해줌
    }
}

stomp 로그인 인증 처리

http 프로토콜이 아니기 때문에 securityConfig 가아닌 web socket을 이용하여 인증 인터셉터 등록
StompWebSocketConfig에 아래와같이 매소드 생성

  @Override
    //소켓요청 connect, subscribem disconnect 등의 요청시에는 http header 등 http 메시지를 넣어올수 있고 이를 intercepter가
    //가로채 토큰등을 검증가능
    public void configureClientInboundChannel(ChannelRegistration registration) {
       registration.interceptors(stompHandler);
    }
  • handdler
@Component
@Slf4j
public class StompHandler implements ChannelInterceptor {
    @Value("${jwt.secretKey}")
    private String secretKey;
    //connect disconnect 와 같은 요청이 둘어옴
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        final StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

        if(StompCommand.CONNECT==accessor.getCommand()){
            log.info("connect요청시 토큰 유효성 검증");
            String bearerToken = accessor.getFirstNativeHeader("Authorization");
            String jwtToken = bearerToken.substring(7);
            //토큰 검증
            Claims claims = Jwts.parserBuilder()
                    .setSigningKey(secretKey) //
                    .build()
                    .parseClaimsJws(jwtToken)
                    .getBody();
           log.info("토큰 검증 완료");
        }


        return message;
    }
}
profile
잘하자

0개의 댓글