WebSocket 서버 구현하기
태그: WebSocket
카테고리: Spring
업데이트:
기본적으로 HTTP 프로토콜을 이용한 통신은 클라이언트가 서버에 요청을 보내면, 서버는 그 요청에 대해 응답한 뒤 연결을 끊는 방식으로 진행됩니다. 이 방법은 서버가 먼저 클라이언트에 데이터를 보내는 방법을 구현하기 어렵습니다. 또 통신 빈도가 많은 경우 전송을 할 때마다 연결한 뒤 전송하고, 연결 해지하는 과정을 거치기 때문에 효율이 많이 떨어집니다.
이러한 방법을 해결하기 위해 웹 소켓을 사용하기 시작했습니다. 해당 프로토콜 요청은 [ws://주소] 형식으로 요청합니다.
1. 라이브러리 추가하기
build.gradle에 다음 Dependency를 추가합니다.
implementation 'org.springframework.boot:spring-boot-starter-websocket'
2. WebSocket Handler 구현하기
이번에는 서버에서 다수의 클라이언트가 보낸 메시지를 처리하는 핸들러를 구현해 보겠습니다.
텍스트 기반 채팅을 구현하기 위해 TextWebSocketHandler를 상속받아 구현하겠습니다.
@Component
public class ChatHandler extends TextWebSocketHandler {
private static List<WebSocketSession> sessionList = new ArrayList<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// Message를 수신받은 경우 처리하는 메서드
String payload = message.getPayload(); // 전송되는 데이터
// 수신받는 메시지를 그대로 사용자들에게 전송하는 경우
for(WebSocketSession s: sessionList) {
s.sendMessage(message);
}
// 서버에서 메시지를 가공한 뒤 전송하는 경우
String processedMsgStr = processMsg() // 임의의 함수
TextMessage processedMsg = new TextMessage(processedMsgStr.getBytes());
for(WebSocketSession s: sessionList) {
s.sendMessage(processedMsg);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 클라이언트가 접속 할 경우 호출되는 메서드
sessionList.add(session);
System.out.println(session + " 클라이언트 접속");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 클라이언트와 접속이 끊어진 경우 호출되는 메서드
System.out.println(session + " 클라이언트 접속 해제");
sessionList.remove(session);
}
}
3. WebSocket Config 구현하기
Handler를 이용해 WebSocket을 활성 하기 위해 Config를 구현해 보겠습니다.
@EnableWebSocket 어노테이션을 사용해 활성화를 합니다.
@Configuration
@RequiredArgsConstructor
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
private final ChatHandler chatHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// ws://서버주소/ws/chat 주소로 WebSocket을 연결 가능하도록 설정
// setAllowedOrigins("*") : CORS를 허용
registry.addHandler(chatHandler, "ws/chat").setAllowedOrigins("*");
}
}
WebSecurity + JWT를 사용하는 경우
기존에 프로젝트에서 인증 기능을 구현하면서 WebSecurity + JWT를 사용한 경우 Token을 헤더에 포함시켜 인증을 진행합니다. 하지만 일반적으로 웹 소켓에서 연결 요청을 하는 경우 헤더에 인증 토큰을 추가하기 어렵기 때문에 WebSocket을 기본적으로 허용해 주고 ChatHandler에서 별도로 인증 검사를 하는 방식으로 구현해야 합니다.
따라서 다음과 같이 WebSecurityConfig에서 허용을 해줍니다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class WebSecurityConfig {
... 중략
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests((requests) -> requests
.antMatchers(허용한 요청들..., "/ws/chat").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class)
);
return http.build();
}
}
그리고 ChatHandler에서 다음과 같이 검사합니다.
@Component
@RequiredArgsConstructor
public class ChatHandler extends TextWebSocketHandler {
private final JwtTokenProvider jwtTokenProvider;
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
... 중략
String token; // message에서 token 가져오기
if(!jwtTokenProvider.validateToken(token)) {
// 인증 실패 한 경우에 대한 처리
}
// 메시지 처리
... 중략
}
}
댓글남기기