[TS] 실시간 채팅 시 권한 관리 개선

2026. 4. 17. 00:39·programming/ts

1. 서론

채팅 기능 개발을 하던 도중 문득 그런 생각이 들었다.
현재 플로우는 사용자가 공방에 제작 의뢰를 하면 DB에 채팅방이 생성되고 생성된 고유 id 값을 STOMP 채팅 시의 구독경로로 넣어야 한다.

 

여기서 의문점.

  1. 그럼 채팅을 보낼때마다 DB에서 조회를 해야 하는가?
  2. 그럼 채팅을 보낼때마다 DB에 채팅 내역을 저장해야 하는가?

 

2. 본론

1번 해결

일단 1번은 확실히 아닌 것 같다.


DB 조회 연산이 많아지면 서버가 느려지는것 정도는 어느정도 생각했고, 지금이야 테스트 하고 있어서 돌아가겠지만 사용자가 4명일 때 채팅만 해도 한번 채팅 주고 받을 때 조회 4번, 저장 4번 총 8번이 생긴다.

 

나의 생각은 이랬다.

 

'소켓을 연결할 때 커스텀 헤더에서 토큰 값을 추출하는데, 어짜피 채팅을 한번 하고 끊고 한번 하고 끊을 게 아니라면 소켓 연결 시에만 DB 조회를 해서 세션에 {user : ok} 이런식으로 저장해두고 세션에 user 키 유무만 확인하면 되지 않을까?'

 

StompHandler.java

@Slf4j
@RequiredArgsConstructor
@Component
@Order(Ordered.HIGHEST_PRECEDENCE + 99) // 스프링의 빈 순서를 지정하는 애노테이션으로, StompHandler 의 우선순위를 설정한다.
public class StompHandler implements ChannelInterceptor {
    private final JwtTokenProvider jwtTokenProvider;

    /**
     * preSend 는 메시지가 채널로 전송되기 전에 호출되는 메소드.
     * @param message payload + Headers
     * @param channel
     * @return
     */
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
        // 메세지의 STOMP 헤더를 쉽게 접근하기 위한 유틸리티 클래스

        // websocket 연결시 헤더의 jwt token 유효성 검증
        if(StompCommand.CONNECT == accessor.getCommand()){
            log.info("[stomphandler] extract header");
            String token = accessor.getFirstNativeHeader("Authorization");
            log.info("[stomphandler] token : {}",token);
            if(!token.isEmpty()){
                token = token.replace("Bearer ","").trim();
                String nickName = jwtTokenProvider.getUserNickName(token);
                log.info("[stomphandler] nickname : {}",nickName);

                accessor.getSessionAttributes().put("senderNickname", nickName); // 기존 헤더에 nickname 정보를 추가해 저장한다.
            }else{
                throw new TokenNotFoundException("토큰이 필요합니다.");
            }

        }
        return message;
    }
}

accessor.getSessionAttributes().put("senderNickname", nickName); 이 부분에서 소켓 연결을 할 때 STOMP 세션 속성에 메세지 송신자의 닉네임을 저장해둔다.

 

ChatController.java

@MessageMapping("/gagu-chat/{roomNumber}") // mapping ex)/pub/gagu-chat/{roomNumber}
    public void sendMessage(RequestChatContentsDto message,
                                         SimpMessageHeaderAccessor accessor,
                                         @DestinationVariable Long roomNumber) throws Exception {

        log.info("[chat] room id : {}",roomNumber);

        String nickname = (String) accessor.getSessionAttributes().get("senderNickname");
        log.info("[chat] check memeber....");
        if (nickname == null) {
            throw new IllegalArgumentException("세션에 닉네임이 없습니다.");
        }

        Long sessionRoomId = (Long) accessor.getSessionAttributes().get("chatRoomId");

        if(sessionRoomId==null){ // 처음 메세지 보내는 경우
            log.info("[chat] chatting session is empty room id : {}, nickname : {}", roomNumber,nickname);
            User user = userRepository.findByNickName(nickname);
            if(user == null){
                throw new NotFoundUserException();
            }
            Optional<ChatRoom> foundChatRoomList = chatRoomRepository.findById(roomNumber);
            if(!foundChatRoomList.isEmpty()){
                ChatRoom chatRoom = foundChatRoomList.get();
                if(checkMember(chatRoom, user)){ // 채팅방 권한이 있다면
                    accessor.getSessionAttributes().putIfAbsent("chatRoomId", roomNumber);// 세션에 저장되어 있지 않을때, 세션에 저장
                    log.info("[chat] successfully put room id to session");
                }
                log.info("[chat] complete check member");
                Thread.sleep(1000); // 비동기적으로 메시지를 처리하기 위해서 1초 지연(옵션)
                log.info("[chat] question : {}", message.getContents());
                ResponseChatDto responseChatDto = chatService.sendContents(message,roomNumber,nickname);
                template.convertAndSend("/sub/chatroom/"+roomNumber,responseChatDto); // 구독하고 있는 채팅방에 전송
            }else{
                throw new ChatRoomNotFoundException();
            }
        }else{
            log.info("[chat] complete check member");
            Thread.sleep(1000); // 비동기적으로 메시지를 처리하기 위해서 1초 지연(옵션)
            log.info("[chat] question : {}", message.getContents());
            ResponseChatDto responseChatDto = chatService.sendContents(message,roomNumber,nickname);
            template.convertAndSend("/sub/chatroom/"+roomNumber,responseChatDto); // 구독하고 있는 채팅방에 전송
        }
    }
  1. accessor.getSessionAttributes().get("senderNickname"); 를 통해 소켓 연결 시 미리 넣어 두었던 송신자 닉네임을 확인한다.
  2. 메세지를 처음 보내는 경우에는 DB 조회를 해서 메세지 송신자가 채팅방에 속해 있는지 확인한다. 만약, 정상적으로 확인되면 {nickname : 방번호} 형식으로 세션에 넣고 만약 이미 세션에 닉네임을 키로 가진 채팅방 번호가 있을 때 DB 조회 없이 메세지를 전송한다.

이렇게 관리 하니 처음보다 훨씬 DB 조회 연산이 줄었다!

 

https://github.com/MinkeySon/GAGU-SERVER

 

 

 

'programming > ts' 카테고리의 다른 글

[TS] 3D-랜더링 시 텍스처 파일 분리  (1) 2026.04.16
[TS] 3.x.x maven 빌더 Lombok cannot find symbol  (2) 2024.12.12
'programming/ts' 카테고리의 다른 글
  • [TS] 3D-랜더링 시 텍스처 파일 분리
  • [TS] 3.x.x maven 빌더 Lombok cannot find symbol
hand-mk
hand-mk
  • hand-mk
    보조기억장치
    hand-mk
  • 전체
    오늘
    어제
    • 분류 전체보기 (35)
      • 회고록 (2)
      • programming (29)
        • se (5)
        • algorithm (7)
        • ai (3)
        • scm (1)
        • backend (10)
        • ts (3)
      • 공부 (4)
        • 도메인 (3)
        • 자격증 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Github
  • 공지사항

  • 인기 글

  • 태그

    python
    leetcode
    vmware
    exaone3.5
    springboot
    vectordb
    KoNLPy
    폐쇄망
    java
    linux
    Cloudflare
    WSL
    codesignal
    ubuntu
    ollama
    Nest
    node
    코테
    telegraf
    docker
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
hand-mk
[TS] 실시간 채팅 시 권한 관리 개선
상단으로

티스토리툴바