1.1. Full Source

2. SpringBoot Websocket Without Security

  • dependencies
    implementation 'org.springframework.boot:spring-boot-starter-websocket'

2.1.1. 1. Summary

1) ChatController

  • Chat Endpoint
    2) ChatHandshakeHandler
  • Socket Handshake가 이루어 질 때 query string으로 받은 username으로 principal만들어서 주입!
    3) ChatMessage
  • Chat DTO
    4) ChatPrincipal
  • username principal
    5) WebSocketConfig
  • Websocket 관련 설정

2. Detail

1) WebSocketConfig

  • registerStompEndpoints : 웹소켓 연결 엔드포인트를 등록. Handshake가 일어날 때 principal를 주입시키기 위해 ChatHandshakeHandler를 등록한다 ( setHandshakeHandler(new ChatHandshakeHandler()) ).
  • configureMessageBroker : 도착경로 prefix, 메시지 브로커 등 등록
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

   @Override
   public void configureMessageBroker(MessageBrokerRegistry config) {
      /*
       * setApplicationDestinationPrefixes
       * 도착 경로에 대한 prefix를 설정
       * /app : /topic/chat 이라는 구독을 신청했을 때 실제 경로는 /app/topic/chat
       */
      config.setApplicationDestinationPrefixes("/app");
      /*
       * enableSimpleBroker
       * 메시지 브로커 등록
       * 네이밍 : 보통 broadcast는 /topic, 특정 유저에게 보내는 것은 /queue
       */
      config.enableSimpleBroker("/queue");
      /*
       * setUserDestinationPrefix
       * 특정 유저에게 보낼 때(convertAndSendToUser) prefix
       * default : /user
       */
      // config.setUserDestinationPrefix("/user");
   }

   @Override
   public void registerStompEndpoints(StompEndpointRegistry registry) {
      /*
       * socket 연결 엔드포인트
       * 이번엔 ChatHandshakeHandler를 등록 해 ChatHandshakeHandler에서 principal 주입!
       */
      registry.addEndpoint("/ws")
              .setAllowedOriginPatterns("*")
              .setHandshakeHandler(new ChatHandshakeHandler())
              .withSockJS();
   }

}

2) ChatHandshakeHandler

  • WebSocketConfig에서 등록한 socket handshake handler
  • 웹소켓 연결 요청에서 쿼리스트링에 함께 날라온 username으로 principal을 만들어서 주입 해준다.
/*
 *  Socket Handshake가 이루어 질 때 query string으로 받은 username으로 principal만들어서 주입!
 * ex) http://localhost/ws?test => username : test
 */
public class ChatHandshakeHandler extends DefaultHandshakeHandler {

   @Override
   protected Principal determineUser(ServerHttpRequest request,
                                     WebSocketHandler wsHandler,
                                     Map<String, Object> attributes) {
      String username = request.getURI().getQuery();
      return new ChatPrincipal(username);
   }
}

3) ChatController

  • 클라이언트의 요청 Endpoint
  • @SendToUser 대신 convertAndSendToUser 메서드 사용이유 : SendToUser는 security와 함께 사용!
  • 클라이언트가 웹소켓 연결 후 /chat.send으로 메시지를 담아서 요청하면 principal에서 username을 가져와 sender에 담은 후 스트림을 구독중인 receiver에게 전달!
@Controller
public class ChatController {

    @Autowired
    private SimpMessagingTemplate messageTemplate;

    private static final Logger logger = LoggerFactory.getLogger(ChatController.class);

    @MessageMapping("/chat.send") // 요청 엔드포인트 /app/chat.send
    @SendTo("/queue/pub")
    public void sendMessage(@Payload ChatMessage message, Principal principal) {
        /*
         * 특정 유저에게만 보내기!
         * 구독할 때는 /user/topic/pub
         */
        message.setSender(principal.getName());
        logger.info("Sender : " + message.getSender() + ", Receiver : " + message.getReceiver() + ", Content : "+message.getContent());
        messageTemplate.convertAndSendToUser(message.getReceiver(), "/queue/pub", message);
    }

}
복사했습니다!