Feat: 채팅 WebSocket Redis Pub/Sub 연동 및 구조 개선#112
Conversation
Redis Pub/Sub 연동 (다중 서버 대응)
- ChatRedisPublisher: 메시지 저장 후 Redis "chat:room:{roomId}" 채널에 발행
- ChatRedisSubscriber: Redis 채널 구독 후 WebSocket 구독자에게 브로드캐스트
- 기존 SimpleBroker 직접 브로드캐스트 → Redis 경유 방식으로 전환
- 서버 A에서 발행한 메시지를 서버 B의 구독자도 수신 가능
RedisConfig 수정
- 채팅 전용 chatRedisTemplate 빈 추가 (JSON 직렬화)
- JavaTimeModule 등록으로 LocalDateTime 직렬화 지원
- PatternTopic("chat:room:*")으로 모든 채팅방 채널을 단일 리스너로 처리
- RedisMessageListenerContainer 빈 등록
WebSocket 예외 처리 전역화
- ChatWebSocketController 내부 @MessageExceptionHandler 제거
- WebSocketExceptionHandler를 common/exception 패키지로 분리
- @ControllerAdvice + @MessageExceptionHandler로 전역 처리
- CustomException → WARN 레벨, 그 외 Exception → ERROR 레벨 분리
ChatErrorResponse DTO 분리
- ChatWebSocketController 내부 record 선언 제거
- domain/chat/dto/response/ChatErrorResponse.java로 독립 분리
SUBSCRIBE 인가 검증 추가
- StompChannelInterceptor에 SUBSCRIBE 시점 Principal 존재 여부 확인 추가
- 인증되지 않은 세션의 채널 구독 시도 차단
- SockJS 폴백과 무관한 레벨(STOMP 프레임 처리 단계)에서 수행
모든 상태 변경 메서드(closeChatRoom, joinChatRoom)에 비관적 락 적용
- findByIdForUpdate()로 SELECT FOR UPDATE 처리
- 고객 종료와 관리자 참여 동시 요청 시 데이터 정합성 보장
🤖 AI 코드리뷰
🟡 개선 제안1. } catch (Exception e) {
log.error("[Redis Sub] 메시지 처리 실패", e);
}운영 환경에서 메시지 역직렬화가 실패하거나, WebSocket 브로드캐스트 도중 장애가 발생하면 해당 메시지는 그냥 소리 소문 없이 유실됩니다. 채팅 서비스에서 메시지가 드문드문 증발하는 것보다는 명시적으로 실패를 알리는 것이 낫습니다. 최소한 Dead Letter Queue(DLQ)로 보내거나, 복구 가능 여부에 따라 애플리케이션 로직을 중단시키는 방향으로 설계해야 합니다. "예외 삼키기"는 절대 용납되지 않습니다. 2. 3. WebSocket SUBSCRIBE 권한 검증 미흡 🔴 보안 / 성능 주의1. Deserialization 취약점 (Remote Code Execution 위험) objectMapper.activateDefaultTyping(
objectMapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL
);
2. Redis Pub/Sub + SimpleBroker 혼용의 구조적 함정 💡 학습 포인트
🤔 생각해보기현재 코드는
|
💡 개요
🛠️ 작업 내용
Redis Pub/Sub 연동 (다중 서버 대응)
RedisConfig 수정
WebSocket 예외 처리 전역화
ChatErrorResponse DTO 분리
SUBSCRIBE 인가 검증 추가
모든 상태 변경 메서드(closeChatRoom, joinChatRoom)에 비관적 락 적용
findByIdForUpdate()로 SELECT FOR UPDATE 처리
고객 종료와 관리자 참여 동시 요청 시 데이터 정합성 보장
Closes: [Feat] 실시간 채팅 WebSocket 연결 #109