[ApartTime] 관리자 시스템 인증 흐름

고뭉남·2025년 6월 22일

ApartTime

목록 보기
3/6
post-thumbnail

초기에는 서버가 발행하는 실시간 알림을 SSE로 구현했으나, 추후 채팅 기능 도입 및 양방향 통신의 필요성으로 인해 WebSocket 기반으로 전환했습니다.

초기 SSE 기반: [ApartTime] Admin Server 구축기 - 실시간 회원가입 알림 (Kafka + SSE)


로그인 흐름

아파트타임 관리자 시스템은 JWT 기반의 사용자 인증 방식을 사용합니다.

사용자가 로그인에 성공하면 서버는 다음과 같은 토큰들을 발급합니다.

  • Access Token
    응답 body에 포함되어 클라이언트에 전달
    클라이언트는 이를 local storage에 저장

  • Refresh Token
    HttpOnly Cookie로 클라이언트에 전달
    클라이언트에서 직접 접근 불가능

인증 흐름은 아래 도식과 같습니다.

login_flow


Secondary Token 기반 WebSocket Handshake

클라이언트는 사용자 로그인 직후, 서버로부터 실시간 알림을 수신하기 위해 WebSocket 연결을 시도합니다.

하지만 아파트타임 관리자 시스템의 WebSocket은 인증이 필요한 채널이므로, 인증된 사용자만 알림을 받을 수 있도록 설계해야 했습니다.

문제는 브라우저 환경에서는 WebSocket Handshake 요청에 커스텀 헤더를 추가할 수 없다는 점입니다.

따라서 JWT 기반 인증 환경에서 일반적으로 사용되는, 클라이언트에 저장된 Access Token을 Authorization 헤더에 담아 전달하는 방식은 사용할 수 없었습니다.

대신 쿼리 파라미터를 통해 Access Token을 전달하여 서버에서 검증하는 방식도 고려해봤지만, Access Token이 URL에 노출된다는 점이 마음에 들지 않았습니다.

그렇게 찾게 된 대안 중 하나가 Secondary Token을 발급 받아 WebSocket Handshake 요청 시 쿼리 파라미터로 전달하는 방식이었습니다.

websocket_session_flow

Secondary Token은 만료 기한이 매우 짧게 설정되어 있어 탈취되더라도 위험을 최소화할 수 있습니다.

그리고 만약 실제로 이 토큰이 탈취되어 HTTP API 요청의 Authorization 헤더에 담아 요청을 보내더라도, 서버의 JwtAuthenticationFilter는 토큰 내부의 type 클레임을 통해 Access Token만을 필터링 대상으로 허용하도록 구성했기에 문제가 없습니다.

// Secondary Token

{
  "sub": "1",
  "type": "Secondary Token",
  "iat": 1750337929,
  "exp": 1750337939
}

// Access Token

{
  "sub": "1",
  "type": "Access Token",
  "iat": 1750337929,
  "exp": 1750338279
}

Silent Refresh + RTR 적용

초기에는 일반적인 방식대로, 클라이언트가 서버로 API 요청을 보낼 때 서버 측에서 Access Token의 유효성을 검사하고, 만료된 경우 401 Unauthorized 응답을 내려 Silent Refresh를 유도하는 구조로 구현했습니다.

하지만 이 방식을 그대로 유지하기에는 문제가 있었습니다.

아파트타임 관리자 시스템은 현재 WebSocket을 통해 서버 → 클라이언트 방향으로 실시간 알림을 전송하는 구조인데, 만약 클라이언트가 WebSocket만 유지한 채 별도의 API 요청을 서버로 보내지 않으면 Access Token이 만료되더라도 서버에서는 이를 인지할 방법이 없고, 결국 인증이 만료된 사용자에게 계속해서 실시간 알림이 전송될 수 있다는 점이었습니다.

따라서 이를 해결하기 위해 클라이언트가 능동적으로 Access Token의 만료 시점을 감지할 수 있도록 클라이언트 단에서 동작하는 타이머 기반 Silent Refresh로 수정했습니다.

클라이언트는 로그인 이후 발급 받은 Access Token을 디코딩하여 내부에 기록된 만료 시간을 확인합니다.

그리고 해당 시간보다 1분 먼저 종료되는 타이머를 설정하여 Access Token의 만료 1분 전에 서버에 Silent Refresh 요청을 보내게 설정합니다.

요청은 클라이언트에 저장된 Refresh Token을 기반으로 /api/auth/reissue 엔드포인트에 전달되며, 서버가 여기에서 Refresh Token을 검증한 후 새로운 Access Token을 발급합니다.

이때 아파트타임 관리자 시스템은 RTR(Refresh Token Rotation) 전략도 함께 적용하고 있습니다.

결과적으로 클라이언트 단에서 Silent Refresh가 수행될 때마다 서버는 새로운 Access Token과 함께 새로운 Refresh Token을 발급하며, 기존 Refresh Token은 더 이상 재사용할 수 없도록 Redis에서 삭제됩니다.

Silent Refresh + RTR의 흐름을 표현한 도식은 아래와 같습니다.

silent_refresh_flow

profile
개발자 고뭉남입니다.

0개의 댓글