access token
과 refresh token
을 같이 쓰는 가장 큰 이유는 보안이다.
access token
은 만료 기간을 짧게 주어 access token
이 만에 하나 탈취 당하더라도 짧은 시일내에 해당 토큰이 만료되어 큰 문제가 발생하지 않도록 한다.
그러나 이렇게 access token
의 만료 기간을 짧게 주면 생기는 문제점은, 계속해서 만료될때마다 해다 서비스의 인증
절차를 통해 다시 access token
을 발급 받아야 되는 번거로움이 생긴다는 것이다.
한 마디로, access token
의 만료 기간이 10분이면, 10분마다 다시 재로그인을 해야 된다는 것이다.
이러한 단점을 보완하고자 나온 것이 refresh token
이다.
refresh token
은 만료 기간을 2주에서 길게는 4주로 준다.
그러면 유저는 access token
이 만료되면, refresh token
을 서버에 보내서 다시 access token
을 받게 된다.
즉, refresh token
은 로그인 대신에 사용자를 인증하는 수단이 된다.
하지만 이렇게 긴 만료 기간을 가진 refresh token
을 탈취 당하면 어떻게 될까?
이를 탈취한 유저는 해당 기간동안 탈취한 토큰의 사용자인척 서비스를 계속해서 이용할 수 있게 된다.
어떻게 보면, 편리함을 위해 보안을 일부 포기한 것으로 볼 수 있다.
하지만 보안은 서비스에서 매우 중요한 부분이므로 이를 그냥 포기하면 안된다.
그러면, 인증의 편리함을 챙기는 동시에 보안을 챙기기 위해선 어떻게 해야할까?
바로 refresh token
은 DB를 통해 관리하면 된다.
refresh token
을 DB에서 관리하면 생기는 보안적 이점은 무엇이 있을까?
만약 refresh token
을 DB에서 관리하지 않으면 해당 토큰을 탈취당해도 이를 제제할 수단이 없다.
즉, 눈 뜨고 코를 베여도 아무런 조치를 할 수 없다는 것이다.
이것이 “인가”의 문제점이다. 해당 토큰에 대해 권한을 부여하고 이를 제어할 수단이 없으므로 보안적으로 매우 취약하게 된다.
그래서 DB를 이용해서 해당 토큰을 관리하게 된다.
그러면 서버에서는 refresh token
을 받으면 DB에 해당 토큰이 기록되어 있는지 확인한다.
만약 기록된 토큰이라면 정상적인 유저임을 확인하고 통과시켜준다.
그런데 만약 그렇게 통과시킨 유저가 알고보니 서비스를 위험하게 만드는 사용자라면?
DB에서 해당 refresh token
기록을 삭제하면 된다. 그러면 해당 토큰은 서비스에서 없는 토큰이 되어 다시 로그인 할 수 없게 된다.
해당 작업을 이번에 진행했다…
RedisClient & RedisService 생성
RedisService 테스트 코드 작성
GCP MemoryStore Redis 학습 및 Private Service Connect 진행
MySQL RefreshToken Repository 생성 ( Redis 백업용 )
Backup 테스트 코드 작성 및 테스트 진행
SELECT BOARD.TITLE, REPLY.BOARD_ID, REPLY.REPLY_ID, REPLY.WRITER_ID, REPLY.CONTENTS,
DATE_FORMAT(REPLY.CREATED_DATE, '%Y-%m-%d') AS CREATED_DATE
FROM USED_GOODS_REPLY AS REPLY
LEFT JOIN USED_GOODS_BOARD AS BOARD ON REPLY.BOARD_ID = BOARD.BOARD_ID
WHERE BOARD.CREATED_DATE BETWEEN '2022-10-01' AND '2022-10-31'
ORDER BY REPLY.CREATED_DATE ASC, BOARD.TITLE ASC
def solution(nums):
num_choice = len(nums)//2
num_monster = len(set(nums))
return num_monster if num_choice > num_monster else num_choice
GCP 자체에서 MemoryStore Redis를 지원해줘서 해당 서비스를 이용했다.
맨 처음 GCP에 redis 인스턴스를 만들었을 때, IP 주소가 있길래 나는 외부 접근이 가능한 외부 IP인줄 알고 해당 IP로 접근을 했다. 하지만 당연하게도 해당 IP로 접근할 수 없었다.
GCP에서는 보안 문제로 Redis 인스턴스에 외부 IP를 지원하지 않는다고 한다.
알고보니 내가 이용한 IP 주소는 내부 IP 주소였다.
즉, 외부에서 redis endpoint로 바로 접근이 불가능하다. 이를 위해서 Private Service Connect
방식을 통해 해당 Redis 인스턴스에 접근할 수 있다.
refresh token
을 DB에 저장하고 DB에 해당 값이 있는지 체크하고 나서 access token
을 재발급 해주는 방식으로 로직을 작성했다.
해당 방식으로 진행하게되면, 이미 서비스를 이용중이던 사용자들이 모두 로그아웃이 된다는 것이다. (refresh token
을 통한 access token
재발급 불가능)
나는 막연하게 그냥 기존 유저들이 다시 한번 로그인하면 되는 게 아닌가? 라는 안일한 생각을 했다.
그때 사수분이 해당 부분이 만족되지 않으면, 여태까지 너가 잘 짜놓은 구조라도 서비스에 배포해서 나갈 수 없다고 강하게 말씀하셨다.
나는 솔직히 이때 왜?라는 의문이 들었다.
그냥 유저들이 다시 한번 로그인하면 될 문제가 아닌가? 싶었다.
많은 서비스들이 에러로 인해 작게는 데이터 손실부터 크게는 서비스 중지가 되는데, 고작 이런 게 뭐가 문제일까? 라는 생각이었다.
그런데 사수분이 로그인하는 과정은 사용자에게 큰 부담이고, 너 한명 고생하면 서비스 수백명의 이용자가 편해질 수 있는 것이다. 라고 말씀해주셨고, 순간 머리가 멍해졌다.
나는 사용자가 다시 한번 로그인 하는 경우를 사람 “한 명”으로 한정지어서 생각했다.
생각해보니 서비스에는 수많은 사용자가 있고 이는 “수백명” 아니 더 나아가 “수만명”이 될 수 있다.
이때, 새로운 기능을 배포해서 오류로 인해 수만명의 특정한 동작을 요구해야 한다?
이거는 서비스에 중대한 오류라고 볼 수 있는 심각한 상황인 것이다.
“서비스”의 입장이 아닌 “개인”의 입장으로 보니 한없이 작았던 게, 서비스 전체적으로 보니 중대한 문제가 될 수 있음을 다시 한번 깨닫게 되었다. 사수님 감사합니다…! 사수 최고…! 킹왕짱…!
그래서 refresh token
에 version 정보를 payload에 추가해서 처리하기로 했다.
token version 값이 undefined → (구) 토큰 사용자 → 일단은 예전 로직대로 로그인 시킴
새로운 버젼의 refresh token
을 access token
과 함께 반환
(구) 토큰은 새로운 버젼의 토큰으로 갱신되어 새로운 로직에 따라 동작
해당 서비스 플로우대로 해당 문제를 처리했다!
이 경험은 정말 의미있던 경험인 것 같아서 따로 다시 한번 정리해서 블로그에 올리자!