STOMP로 WebSocket 구현을 위해 Spring Boot 사용하기

게시 됨: 2022-03-11

WebSocket 프로토콜은 애플리케이션이 실시간 메시지를 처리하도록 하는 방법 중 하나입니다. 가장 일반적인 대안은 긴 폴링 및 서버 전송 이벤트입니다. 이러한 각 솔루션에는 장점과 단점이 있습니다. 이 기사에서는 Spring Boot Framework를 사용하여 WebSocket을 구현하는 방법을 보여 드리겠습니다. 저는 서버 측 설정과 클라이언트 측 설정을 모두 다룰 것이며 WebSocket 프로토콜을 통해 STOMP를 사용하여 서로 통신할 것입니다.

서버측은 순수하게 Java로 코딩됩니다. 그러나 클라이언트의 경우 일반적으로 WebSockets 클라이언트가 프론트 엔드 애플리케이션에 내장되어 있으므로 Java와 JavaScript(SockJS)로 작성된 스니펫을 보여드리겠습니다. 코드 예제는 pub-sub 모델을 사용하여 여러 사용자에게 메시지를 브로드캐스트하는 방법과 단일 사용자에게만 메시지를 보내는 방법을 보여줍니다. 이 기사의 추가 부분에서 WebSocket 보안과 WebSocket 기반 솔루션이 WebSocket 프로토콜을 지원하지 않는 환경에서도 계속 작동하도록 하는 방법에 대해 간략하게 설명하겠습니다.

WebSocket 보안에 대한 주제는 별도의 기사에서 다루기에 충분히 복잡한 주제이므로 여기서는 간략하게만 다룰 것입니다. 이로 인해 프로덕션의 WebSocket에서 내가 만지는 몇 가지 다른 요소가 있습니까? 섹션에서 마지막 으로 프로덕션 환경에서 이 설정을 사용하기 전에 수정하는 것이 좋습니다 . 보안 조치가 적용된 프로덕션 준비 설정을 위해 끝까지 읽으십시오.

WebSocket 및 STOMP 프로토콜

WebSocket 프로토콜을 사용하면 응용 프로그램 간의 양방향 통신을 구현할 수 있습니다. HTTP는 초기 핸드셰이크에만 사용된다는 것을 아는 것이 중요합니다. 그런 일이 발생하면 HTTP 연결이 WebSocket에서 사용하는 새로 열린 TCP/IP 연결로 업그레이드됩니다.

WebSocket 프로토콜은 다소 낮은 수준의 프로토콜입니다. 바이트 스트림이 프레임으로 변환되는 방법을 정의합니다. 프레임에는 텍스트 또는 이진 메시지가 포함될 수 있습니다. 메시지 자체는 라우팅 또는 처리 방법에 대한 추가 정보를 제공하지 않기 때문에 추가 코드를 작성하지 않고는 더 복잡한 애플리케이션을 구현하기가 어렵습니다. 다행히 WebSocket 사양은 더 높은 애플리케이션 수준에서 작동하는 하위 프로토콜을 사용할 수 있도록 합니다. Spring Framework에서 지원하는 그 중 하나는 STOMP입니다.

STOMP는 Ruby, Python 및 Perl과 같은 스크립팅 언어가 엔터프라이즈 메시지 브로커에 연결하기 위해 처음에 생성된 간단한 텍스트 기반 메시징 프로토콜입니다. STOMP 덕분에 서로 다른 언어로 개발된 클라이언트와 브로커가 서로 메시지를 주고받을 수 있습니다. WebSocket 프로토콜은 웹용 TCP라고도 합니다. 유사하게 STOMP는 웹용 HTTP라고 합니다. WebSocket 프레임에 매핑되는 몇 가지 프레임 유형(예: CONNECT , SUBSCRIBE , UNSUBSCRIBE , ACK 또는 SEND )을 정의합니다. 한편으로 이러한 명령은 통신을 관리하는 데 매우 편리하며 다른 한편으로는 메시지 승인과 같은 보다 정교한 기능으로 솔루션을 구현할 수 있습니다.

서버 측: Spring Boot 및 WebSockets

WebSocket 서버 측을 구축하기 위해 Java에서 독립 실행형 및 웹 응용 프로그램 개발 속도를 크게 높이는 Spring Boot 프레임워크를 활용합니다. Spring Boot에는 Java WebSocket API 표준(JSR-356)과 호환되는 spring-WebSocket 모듈이 포함되어 있습니다.

Spring Boot를 사용하여 WebSocket 서버 측을 구현하는 것은 그리 복잡한 작업이 아니며 몇 가지 단계만 포함합니다.

1단계. 먼저 WebSocket 라이브러리 종속성을 추가해야 합니다.

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>

전송된 메시지에 JSON 형식을 사용하려는 경우 GSON 또는 Jackson 종속성도 포함할 수 있습니다. 아마도 Spring Security와 같은 보안 프레임워크가 추가로 필요할 수 있습니다.

2단계. 그런 다음 WebSocket 및 STOMP 메시징을 활성화하도록 Spring을 구성할 수 있습니다.

 Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/mywebsockets") .setAllowedOrigins("mydomain.com").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config){ config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } }

configureMessageBroker 메서드는 두 가지 작업을 수행합니다.

  1. 메시지를 보내고 받기 위한 하나 이상의 대상이 있는 메모리 내 메시지 브로커 를 만듭니다. 위의 예에는 두 개의 대상 접두사, 즉 topicqueue 가 정의되어 있습니다. 그들은 pub-sub 모델을 통해 구독된 모든 클라이언트에 전달될 메시지의 대상 앞에 topic 접두사가 있어야 한다는 규칙을 따릅니다. 반면에 개인 메시지의 대상에는 일반적으로 queue 접두사가 붙습니다.
  2. 컨트롤러에서 구현할 @MessageMapping 주석이 달린 메서드가 처리하는 대상을 필터링하는 데 사용되는 접두사 app 을 정의합니다. 컨트롤러는 메시지를 처리한 후 이를 브로커에 보냅니다.

Spring Boot WebSocket: 서버 측에서 메시지를 처리하는 방법

서버 측에서 메시지를 처리하는 방법(출처: Spring 문서)


위의 스니펫으로 돌아가서(아마도 withSockJS() 메서드에 대한 호출을 보았을 것입니다) SockJS 폴백 옵션을 활성화합니다. 간단히 말해서 WebSocket 프로토콜이 인터넷 브라우저에서 지원되지 않는 경우에도 WebSocket이 작동하도록 합니다. 이 주제에 대해 조금 더 자세히 설명하겠습니다.

명확히 해야 할 것이 한 가지 더 있습니다. 엔드포인트에서 setAllowedOrigins() 메서드를 호출하는 이유입니다. WebSocket 및 SockJS의 기본 동작은 동일한 출처 요청만 수락하는 것이기 때문에 종종 필요합니다. 따라서 클라이언트와 서버 측이 다른 도메인을 사용하는 경우 이 메서드를 호출하여 둘 사이의 통신을 허용해야 합니다.

3단계 . 사용자 요청을 처리할 컨트롤러를 구현합니다. 주어진 주제를 구독하는 모든 사용자에게 수신된 메시지를 브로드캐스트합니다.

다음은 대상 /topic/news 로 메시지를 보내는 샘플 메서드입니다.

 @MessageMapping("/news") @SendTo("/topic/news") public void broadcastNews(@Payload String message) { return message; }

@SendTo 주석 대신 컨트롤러 내부에서 자동 SimpMessagingTemplate @SendTo 사용할 수도 있습니다.

 @MessageMapping("/news") public void broadcastNews(@Payload String message) { this.simpMessagingTemplate.convertAndSend("/topic/news", message) }

이후 단계에서 Spring Security 프레임워크의 ResourceServerConfigurerAdapter 또는 WebSecurityConfigurerAdapter 와 같이 엔드포인트를 보호하기 위해 몇 가지 추가 클래스를 추가할 수 있습니다. 또한 전송된 JSON을 개체에 매핑할 수 있도록 메시지 모델을 구현하는 것이 종종 유용합니다.

WebSocket 클라이언트 빌드

클라이언트를 구현하는 것은 훨씬 더 간단한 작업입니다.

1단계. Spring STOMP 클라이언트 Autowire.

 @Autowired private WebSocketStompClient stompClient;

2단계. 연결을 엽니다.

 StompSessionHandler sessionHandler = new CustmStompSessionHandler(); StompSession stompSession = stompClient.connect(loggerServerQueueUrl, sessionHandler).get();

이 작업이 완료되면 대상으로 메시지를 보낼 수 있습니다. 메시지는 주제를 구독하는 모든 사용자에게 전송됩니다.

 stompSession.send("topic/greetings", "Hello new user");

메시지 구독도 가능합니다.

 session.subscribe("topic/greetings", this); @Override public void handleFrame(StompHeaders headers, Object payload) { Message msg = (Message) payload; logger.info("Received : " + msg.getText()+ " from : " + msg.getFrom()); }

때로는 전용 사용자에게만 메시지를 보내야 하는 경우가 있습니다(예: 채팅을 구현할 때). 그런 다음 클라이언트와 서버 측에서는 이 개인 대화 전용으로 별도의 대상을 사용해야 합니다. 목적지 이름은 /queue/chat-user123 과 같이 일반 목적지 이름에 고유 식별자를 추가하여 생성할 수 있습니다. 이를 위해 HTTP 세션 또는 STOMP 세션 식별자를 사용할 수 있습니다.

Spring을 사용하면 개인 메시지를 훨씬 쉽게 보낼 수 있습니다. @SendToUser 를 사용하여 Controller의 메소드에 주석을 달기만 하면 됩니다. 그런 다음 이 대상은 세션 식별자에 의존하는 UserDestinationMessageHandler 에 의해 처리됩니다. 클라이언트 측에서 클라이언트가 /user 접두사가 붙은 대상에 가입하면 이 대상은 이 사용자에게 고유한 대상으로 변환됩니다. 서버 측에서 사용자 대상은 사용자의 Principal 에 따라 확인됩니다.

@SendToUser 주석이 있는 샘플 서버 측 코드:

 @MessageMapping("/greetings") @SendToUser("/queue/greetings") public String reply(@Payload String message, Principal user) { return "Hello " + message; }

또는 SimpMessagingTemplate 을 사용할 수 있습니다 .

 String username = ... this.simpMessagingTemplate.convertAndSendToUser(); username, "/queue/greetings", "Hello " + username);

이제 위의 예에서 Java 코드가 보낼 수 있는 개인 메시지를 수신할 수 있는 JavaScript(SockJS) 클라이언트를 구현하는 방법을 살펴보겠습니다. WebSocket은 HTML5 사양의 일부이며 대부분의 최신 브라우저에서 지원된다는 사실을 아는 것은 가치가 있습니다(Internet Explorer는 버전 10부터 이를 지원합니다).

 function connect() { var socket = new SockJS('/greetings'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/user/queue/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).name); }); }); } function sendName() { stompClient.send("/app/greetings", {}, $("#name").val()); }

이미 언급했듯이 개인 메시지를 수신하려면 클라이언트가 /user 접두사가 붙은 일반 대상 /queue/greetings 에 가입해야 합니다. 고유 식별자를 사용하지 않아도 됩니다. 그러나 클라이언트는 그 전에 애플리케이션에 로그인해야 하므로 서버 측의 Principal 개체가 초기화됩니다.

웹소켓 보안

많은 웹 응용 프로그램은 쿠키 기반 인증을 사용합니다. 예를 들어, Spring Security를 ​​사용하여 로그인한 사용자만 특정 페이지 또는 컨트롤러에 대한 액세스를 제한할 수 있습니다. 그런 다음 사용자 보안 컨텍스트는 나중에 해당 사용자에 대해 생성된 WebSocket 또는 SockJS 세션과 연결된 쿠키 기반 HTTP 세션을 통해 유지됩니다. WebSockets 끝점은 Spring의 WebSecurityConfigurerAdapter 와 같은 다른 요청으로 보호될 수 있습니다.

요즘 웹 애플리케이션은 종종 REST API를 백엔드로 사용하고 사용자 인증 및 권한 부여를 위해 OAuth/JWT 토큰을 사용합니다. WebSocket 프로토콜은 HTTP 핸드셰이크 동안 서버가 클라이언트를 인증하는 방법을 설명하지 않습니다. 실제로 이러한 목적으로 표준 HTTP 헤더(예: Authorization)가 사용됩니다. 불행히도 모든 STOMP 클라이언트에서 지원되지는 않습니다. Spring Java의 STOMP 클라이언트는 핸드셰이크에 대한 헤더를 설정할 수 있습니다.

 WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add(principalRequestHeader, principalRequestValue);

그러나 SockJS JavaScript 클라이언트는 SockJS 요청과 함께 인증 헤더 보내기를 지원하지 않습니다. 그러나 토큰을 전달하는 데 사용할 수 있는 쿼리 매개변수를 보낼 수 있습니다. 이 접근 방식을 사용하려면 쿼리 매개변수에서 토큰을 읽고 유효성을 검사하는 사용자 지정 코드를 서버 측에서 작성해야 합니다. 심각한 보안 위반을 초래할 수 있으므로 토큰이 요청과 함께 기록되지 않았는지(또는 로그가 잘 보호되는지) 확인하는 것도 중요합니다.

SockJS 폴백 옵션

WebSocket과의 통합이 항상 원활하게 진행되는 것은 아닙니다. 일부 브라우저(예: IE 9)는 WebSocket을 지원하지 않습니다. 게다가 제한적인 프록시는 HTTP 업그레이드를 수행하는 것을 불가능하게 만들거나 너무 오랫동안 열려 있는 연결을 끊을 수 있습니다. 이러한 경우 SockJS가 구출됩니다.

SockJS 전송은 WebSocket, HTTP 스트리밍 및 HTTP Long Polling의 세 가지 일반적인 범주로 나뉩니다. 통신은 서버에서 기본 정보를 얻기 위해 GET /info 를 보내는 SockJS로 시작됩니다. 응답을 기반으로 SockJS는 사용할 전송을 결정합니다. 첫 번째 선택은 WebSocket입니다. 지원되지 않는 경우 가능하면 스트리밍이 사용됩니다. 이 옵션도 가능하지 않으면 폴링이 전송 방법으로 선택됩니다.

생산 중인 WebSocket?

이 설정은 작동하지만 "최상"은 아닙니다. Spring Boot를 사용하면 STOMP 프로토콜(예: ActiveMQ, RabbitMQ)이 있는 본격적인 메시징 시스템을 사용할 수 있으며 외부 브로커는 우리가 사용한 단순 브로커보다 더 많은 STOMP 작업(예: 승인, 수신)을 지원할 수 있습니다. STOMP Over WebSocket 은 WebSocket 및 STOMP 프로토콜에 대한 흥미로운 정보를 제공합니다. 여기에는 STOMP 프로토콜을 처리하고 프로덕션에서 사용하기에 더 나은 솔루션이 될 수 있는 메시징 시스템이 나열됩니다. 특히 요청이 많아 메시지 브로커를 클러스터링해야 하는 경우. (Spring의 단순 메시지 브로커는 클러스터링에 적합하지 않습니다.) 그런 다음 WebSocketConfig 에서 단순 브로커를 활성화하는 대신 외부 메시지 브로커와 메시지를 주고받는 Stomp 브로커 릴레이를 활성화해야 합니다. 요약하자면, 외부 메시지 브로커는 보다 확장 가능하고 강력한 솔루션을 구축하는 데 도움이 될 수 있습니다.

Spring Boot를 탐색하는 Java 개발자 여정을 계속할 준비가 되었다면 다음으로 Using Spring Boot for OAuth2 및 JWT REST Protection을 읽어보십시오.