Spring + RabbitMQ를 통한 1:1 채팅방 구현 - 인증 적용 (4)

2024. 11. 16. 21:20·Project/RabbitMQ(STOMP)를 적용한 1:1 채팅
 

GitHub - lsh2613/spring-rabbitmq: Spring + RabbitMQ를 활용한 1:1 채팅방 구현

Spring + RabbitMQ를 활용한 1:1 채팅방 구현. Contribute to lsh2613/spring-rabbitmq development by creating an account on GitHub.

github.com

 

1. 인증 방법

이 프로젝트에서는 JWT + Session을 적용하였다.

정확히 인증은 JWT를 활용한다

 

2. JWT는 Session 대신 사용하는 건데 왜 굳이 둘 다 사용할까?

먼저 JWT의 인증 방식에 대해 알아 보자

프로젝트마다 약간 다르겠지만 본인이 구현하는 방식은 아래와 같다

HTTP 요청 -> JwtAuthenticationFilter -> header의 token 추출 -> 유효성 검사 및 memberId 추출 -> SecurityContextHolder에 저장 -> member가 필요한 api에서 꺼내어 사용

 

여기서 중요한 점은 SecurityConextHolder는 Http Request를 처리하기 위해 하나의 스레드를 할당받고 SecurityConextHolder에 저장되는 authentication은 ThreadLocal에 저장되었다가 요청이 처리되면 스레드를 반환한다.

즉, http 요청이 처리되어 스레드가 반환되면 SecurityConextHolder에 저장된 authentication(memberId)를 사용할 수 없다는 뜻이다.

 

그럼 이게 왜 중요하다는 걸까?


HTTP와 STOMP의 동작 방식을 먼저 살펴보면 연결 수립하는 핸드셰이크 과정에서 HTTP를 사용하고 이후에는 WS frame을 통해 통신한다. 

'나는 stomp 연결 할 때 jwt의 유효성만 검증하겠다' 하면은 STOMP-CONNECT 시 헤더에 jwt를 넣고 ChannelInterceptor나 @EventListner를 활용하여 유효성 검사가 되도록 구현하면 된다.

 

하지만 나의 프로젝트에서는 stomp가 connect 될 때 memberId, chatRoomId을 받아와 채팅방 입장 처리를 해야 하고, disconnect 될 때 채팅방 퇴장 처리를 해야 한다. 이를 http-jwt 인증 방식 그대로 SecurityContextHolder에 저장해두고 사용하게 되면 disconnect 시 데이터를 가져올 수 없다(stomp는 http가 아니기 때문). 또한, disconnect는 header를 추가할 수 없어 ws이 연결되어 있는 동안 memberId, chatRoomId를 저장해둬야 한다.

 

이 데이터들은 채팅방을 접속해 있는 동안에만 사용되어 데이터 수명이 짧고, 채팅을 보내는 과정에서 빈번히 사용된다는 특징이 있기 때문에 영구 db를 사용하는 것보다는 데이터 생명 주기에 맞춰 stomp-session에 관리하기로 결정했다.

http-session이 아닌 stomp-session이다.

 

3. 설계

앞서 언급했 듯이 stomp 메시지는 ChannelInterceptor나 @EventListner을 통해 잡을 수 있다

나의 프로젝트 에서는 @EventListner를 통해 채팅방 입장/퇴장 로직을 처리하고 있다

// ChatMessageController
@EventListener
public void handleWebSocketConnectListener(SessionConnectEvent event) {
    StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
    chatMessageService.handleConnectMessage(accessor);
}

@EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
    StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage());
    chatMessageService.handleDisconnectMessage(accessor);
}

 

따라서 jwt 검증과 채팅방 입장/퇴장 로직을 분리하기 위해 위 코드를 그대로 냅두고 ChannelInterceptor를 통해 jwt 검증을 처리할 생각이다

 

ChannelInterceptor.preSend(Message<?> message, MessageChannel channel)를 오버라이딩 하면 stomp-connect 시 jwt를 검증하고 session에 memberId와 chatRoomId를 저장할 수 있다

 

4. 구현

JwtAuthenticationInterceptor

@Component
@RequiredArgsConstructor
public class JwtAuthenticationInterceptor implements ChannelInterceptor {

    private final TokenUtil tokenUtil;
    private final StompHeaderAccessorUtil stompHeaderAccessorUtil;

    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);

        if (accessor.getCommand() == CONNECT) {
            String token = stompHeaderAccessorUtil.extractToken(accessor, TokenType.ACCESS_TOKEN);

            Long memberId = tokenUtil.validateTokenAndGetMemberId(token);
            stompHeaderAccessorUtil.setMemberIdInSession(accessor, memberId);

            Long chatRoomId = stompHeaderAccessorUtil.getChatRoomIdInHeader(accessor);
            stompHeaderAccessorUtil.setChatRoomIdInSession(accessor, chatRoomId);
        }

        return message;
    }
}

 

서버로 메시지가 전달 받을 때 인증을 받아야 하니까 WebSockt의 InboundChannel로 설정해준다

 

WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    private final JwtAuthenticationInterceptor jwtAuthenticationInterceptor;

    ...

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(jwtAuthenticationInterceptor);
    }
}

 

 

 

'Project > RabbitMQ(STOMP)를 적용한 1:1 채팅' 카테고리의 다른 글

Spring + RabbitMQ를 통한 1:1 채팅방 구현 - '읽지 않은 유저의 수' 계산(3)  (3) 2024.11.16
Spring + RabbitMQ를 통한 1:1 채팅방 구현 - 채팅내역을 MongoDB로 (2)  (2) 2024.11.01
Spring + RabbitMQ를 통한 1:1 채팅방 구현 (1)  (0) 2024.10.31
'Project/RabbitMQ(STOMP)를 적용한 1:1 채팅' 카테고리의 다른 글
  • Spring + RabbitMQ를 통한 1:1 채팅방 구현 - '읽지 않은 유저의 수' 계산(3)
  • Spring + RabbitMQ를 통한 1:1 채팅방 구현 - 채팅내역을 MongoDB로 (2)
  • Spring + RabbitMQ를 통한 1:1 채팅방 구현 (1)
lsh2613
lsh2613
웹 백엔드 개발자 준비생의 공부일기
  • lsh2613
    Heon's Note
    lsh2613
  • 전체
    오늘
    어제
    • 분류 전체보기 (185)
      • Study (35)
        • Java (0)
        • Spring (14)
        • OOP (4)
        • JPA (12)
        • Design Pattern (3)
        • DB (0)
        • Http & Network (0)
        • Maven (0)
        • Gradle (0)
        • Jenkins (2)
      • DevOps (13)
      • Book Review (0)
        • 자바의 정석 (0)
      • Coding Test (117)
        • 이코테 (5)
        • 백준 (70)
        • 프로그래머스 (37)
        • SW Expert Academy (4)
      • Project (12)
        • WebSocket을 적용한 1:1 채팅 (0)
        • RabbitMQ(STOMP)를 적용한 1:1 채팅 (4)
        • MySQL Spatial Index를 적용한 성능.. (1)
        • Elasticsearch의 전문 검색 인덱스 성능.. (3)
      • Error Solution (6)
      • Review (0)
      • ETC (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
    • 글쓰기
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    STOMP
    AMQP
    rabbitmq
    채팅
    apic
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.0
lsh2613
Spring + RabbitMQ를 통한 1:1 채팅방 구현 - 인증 적용 (4)
상단으로

티스토리툴바