클라이언트 A, B, C는 이미 WebSocket을 통해 서버에 연결되어 있고,
각각 room1이라는 topic(채팅방) 을 구독하고 있다고 가정.
클라이언트는 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를 의미한다
sendMessage(){
if (this.newMessage.trim()==="")return;
this.stompClient.send(`/publish/1`,this.newMessage);
this.newMessage = "";
},
Spring의 SimpleMessageBroker는 메시지를 분석해 해당 room(room1)을 구독 중인 클라이언트를 파악
해당 room(topic)에 메시지를 broadcast(발행) 함
room1을 구독 중이던 모든 클라이언트(B, C 포함)가 메시지를 수신함
클라이언트 측 콜백 함수에서 메시지를 처리하여 UI에 출력함
세션 정보를 직접 서버에서 관리할 필요없이 알아서 클라이언트별 구독 정보를 관리
@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; // 그대로 반환하면 브로커가 전송해줌
}
}
http 프로토콜이 아니기 때문에 securityConfig 가아닌 web socket을 이용하여 인증 인터셉터 등록
StompWebSocketConfig에 아래와같이 매소드 생성
@Override
//소켓요청 connect, subscribem disconnect 등의 요청시에는 http header 등 http 메시지를 넣어올수 있고 이를 intercepter가
//가로채 토큰등을 검증가능
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(stompHandler);
}
@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;
}
}