TIL - 20250915

juni·2025년 9월 15일

TIL

목록 보기
126/316

0915 Full-Stack: Real-time Chat with WebSocket, STOMP & JWT


✅ 1. 백엔드(BE): 실시간 통신 채널 구축 (WebSocket & STOMP)

  • HTTP의 단방향, 비연결성 한계를 극복하고 서버와 클라이언트 간의 지속적인 양방향 통신을 구현하기 위해 WebSocket 기술을 도입했습니다.

➕ 주요 개념

  1. WebSocket:

    • 개념: 하나의 TCP 연결 위에서 클라이언트와 서버가 실시간으로 데이터를 주고받을 수 있게 해주는 통신 프로토콜입니다. 최초 연결 시에만 HTTP를 사용하고, 이후에는 독립적인 WebSocket 프로토콜로 통신합니다.
    • 역할: 실시간 채팅, 알림, 주식 시세 등 서버의 데이터 변경을 즉시 클라이언트에 알려줘야 하는 기능의 기반이 됩니다.
  2. STOMP (Simple Text Oriented Messaging Protocol):

    • 개념: WebSocket 위에서 동작하는 더 높은 수준의 메시징 프로토콜입니다. 순수한 WebSocket 통신은 매우 저수준이라 다루기 복잡하지만, STOMP는 "구독(Subscribe)""발행(Publish)"이라는 직관적인 모델을 제공하여 메시지 흐름을 쉽게 관리할 수 있게 해줍니다.
    • 주요 구성:
      • Message Broker: 메시지를 발행자(Publisher)로부터 받아 구독자(Subscriber)에게 전달하는 중간 관리자 역할을 합니다. (e.g., /topic, /queue)
      • Endpoint: 클라이언트가 WebSocket 연결을 시작하는 경로입니다. (e.g., /ws)
    • WebSocketConfig: Spring에서 STOMP 엔드포인트와 메시지 브로커를 설정하는 Java 설정 클래스입니다.

✅ 2. 백엔드(BE): WebSocket 연결 보안 (JWT 인증)

  • 문제점: WebSocket 연결은 한번 수립되면 계속 유지되므로, 이 연결이 누구로부터 온 것인지 인증하는 것이 매우 중요합니다.
  • 해결책 (Handshake Interceptor): WebSocket 연결은 최초에 HTTP 프로토콜을 통해 "핸드셰이크(Handshake)" 과정을 거쳐 수립됩니다. 이 핸드셰이크 요청을 가로채는 인터셉터를 구현하여 JWT 인증을 수행했습니다.

➕ 인증 흐름

  1. 클라이언트가 WebSocket 연결을 시도하면, 브라우저는 기존에 저장된 Http-Only 쿠키(JWT 포함)를 핸드셰이크 요청에 함께 보냅니다.
  2. 서버의 CookieAuthHandshakeInterceptor가 이 요청을 가로챕니다.
  3. 인터셉터는 쿠키에서 Access Token을 추출하여 유효성을 검증합니다.
  4. 검증에 성공하면, 해당 사용자 정보를 WebSocket 세션에 안전하게 연결합니다.
  5. 이후 해당 WebSocket 연결을 통해 들어오는 모든 메시지는 이 인증된 사용자의 메시지로 간주됩니다.
  6. WebSocketSecurityConfig를 통해, 특정 STOMP 경로(e.g., 메시지 전송)는 인증된 사용자만 접근할 수 있도록 추가적인 보안을 설정했습니다.

✅ 3. 백엔드(BE): 하이브리드 채팅 데이터 제공 (REST + WebSocket)

  • 효율적인 채팅방 경험을 제공하기 위해, 과거 메시지 조회와 실시간 메시지 수신을 두 가지 다른 방식으로 구현했습니다.
  1. 과거 메시지 조회 (REST API):

    • 사용자가 채팅방에 처음 입장했을 때, 이전에 쌓인 대화 내용을 보여주기 위해 페이지네이션(Pagination)이 적용된 REST API(GET /api/chat/rooms/{roomId}/messages)를 구현했습니다.
    • 클라이언트는 이 API를 호출하여 초기 대화 목록을 불러옵니다.
  2. 실시간 메시지 전송 및 수신 (WebSocket):

    • @MessageMapping: 클라이언트가 특정 STOMP 목적지(e.g., /app/chat.sendMessage)로 메시지를 보내면, 이 어노테이션이 붙은 컨트롤러 메서드가 메시지를 수신합니다.
    • 브로드캐스팅(Broadcasting): 컨트롤러는 수신한 메시지를 DB에 저장한 후, 해당 채팅방을 구독하고 있는 모든 클라이언트에게 메시지 브로커를 통해(e.g., /topic/room/{roomId}) 다시 전송(브로드캐스트)합니다.

✅ 4. 프론트엔드(FE): 채팅 클라이언트 구현

  • 백엔드에서 구축한 실시간 통신 채널과 API를 활용하여, 사용자가 실제로 채팅을 주고받을 수 있는 UI와 로직을 구현했습니다.

➕ 주요 개념

  1. WebSocket 연결 관리 (websocketService.js):

    • WebSocket 연결, 구독, 메시지 전송, 연결 해제 등 복잡한 로직을 별도의 서비스 파일로 추상화하여 컴포넌트의 복잡도를 낮췄습니다.
    • 네트워크 불안정 등에 대비하여 자동 재연결 로직을 포함하여 안정성을 높였습니다.
  2. 채팅 UI 컴포넌트:

    • MessageList: 채팅 메시지 목록을 렌더링하는 컴포넌트.
    • MessageItem: 개별 메시지 버블 UI.
    • MessageInput: 사용자가 메시지를 입력하고 전송(Enter) 또는 줄바꿈(Shift+Enter)할 수 있는 입력 컴포넌트.
  3. 데이터 동기화 흐름:

    • [진입] 채팅방 상세 페이지(RoomDetail)에 진입하면, 먼저 REST API를 호출하여 과거 대화 기록을 가져와 화면에 렌더링합니다.
    • [연결] 동시에, websocketService를 통해 WebSocket에 연결하고 해당 채팅방의 STOMP 토픽을 구독합니다.
    • [실시간] 다른 사용자가 보낸 메시지가 브로드캐스트되면, 구독 중인 클라이언트가 이를 수신하여 기존 메시지 목록에 실시간으로 추가합니다.
    • [권한] 스터디룸에 참여한 사용자만 채팅 입력창이 보이도록 조건부 렌더링을 적용했습니다.

📌 요약

  • 백엔드WebSocket과 STOMP를 이용해 실시간 메시징 인프라를 구축했으며, 핸드셰이크 인터셉터를 통해 JWT 쿠키로 WebSocket 연결을 안전하게 인증했습니다.
  • 데이터 제공 방식은 REST API과거 대화 기록을, WebSocket으로 실시간 메시지를 전달하는 효율적인 하이브리드 모델을 채택했습니다.
  • 프론트엔드는 WebSocket 관련 로직을 별도의 서비스로 추상화하여 관리하고, 컴포넌트는 이 서비스를 활용하여 초기 데이터 로딩실시간 업데이트를 자연스럽게 결합하는 방식으로 사용자 경험을 구현했습니다.

0개의 댓글