리프레시 토큰을 사용하는 이유
리프레시 토큰은 액세스 토큰을 재발급하기 위한 용도로 사용합니다.
간략하게 토큰 로그인 과정을 설명해보겠습니다.
유저의 로그인 상태를 유지하기 위해서 서버는 클라이언트에게 액세스 토큰을 줍니다.
클라이언트는 액세스 토큰을 가지고 서버와 통신을 하며 로그인 상태를 보장받습니다.
서버는 토큰을 따로 저장하지 않고 토큰에 담긴 유저 정보를 decode 하여 확인합니다.
즉, 토큰에 담긴 정보를 통해 해당 유저가 로그인 상태라는 것을 인지할 수 있습니다.
하지만 액세스 토큰은 외부에 노출되기 쉽습니다.
액세스 토큰은 빈번히 사용되고 토큰 안에 유저 정보가 있기 때문에 보안상 매우 취약합니다.
이를 해결하기 위해 서버는 액세스 토큰에 만료기간을 부여하여 만약 토큰이 노출되더라도 그 유효시간을 짧게 조절할 수 있습니다. 이때 만료기간이 지난 액세스 토큰을 새롭게 재발급하여 로그인 상태를 유지시키는 것이 리프레시 토큰을 이용하는 주된 이유입니다.
내가 만든 리프레시 토큰
저는 액세스 토큰은 2시간 만료기간을 두었고 토큰 안에는 유저 아이디를 담았습니다.
리프레시 토큰에는 2주일 만료기간을 두었고 토큰 안에는 무의미한 값을 담았습니다.
그리고 이 두 개의 토큰을 최초 로그인 시에 함께 발급하고
이후 액세스 토큰이 만료되어 리프레시 토큰으로 액세스 토큰을 재발급할 때
리프레시 토큰도 새로 생성하여 로그인 때처럼 두 토큰을 함께 발급했습니다.
리프레시 토큰의 경우 액세스 토큰 재발급 용도로만 사용하기 위해서 유저 정보를 안에 담지 않았는데요. 그래도 유효성 확인은 필요했기에 데이터베이스 유저 테이블 안에 리프레시 토큰 컬럼을 추가하여 토큰을 저장하였습니다.
여기서 몇 가지 의문이 생길 것입니다.
왜 리프레시 토큰을 데이터베이스에 저장하는가?
왜 리프레시 토큰 안에는 유저 정보를 담지 않는가?
왜 리프레시 토큰의 만료기간이 끝나지 않았는데 액세스 토큰과 함께 재발급하는가?
제가 이렇게 구현한 이유는 크게 두 가지였습니다.
1. 보안 강화
만료기간이 긴 refresh token을 도중에 새로 발급함으로써 의도적으로 만료기간을 단축시키기 위해서였습니다.
만료기간이 길다는 것은 그만큼 노출 가능성이 높아지기 때문에, 이를 해결하기 위해 리프레시 토큰을 재발급 함으로써 노출 기간을 단축시킨 것입니다.
그러나 만료기간 전에 리프레시 토큰을 재발급한다는 것은 그만큼 사용 가능한 리프레시 토큰이 늘어난다는 얘기가 됩니다. 그래서 서버는 가장 최근 발급한 유효한 리프레시 토큰을 확인할 수 있어야 했고 이 때문에 데이터베이스에 가장 최근 리프레시 토큰을 저장하게 되었습니다.
2. 로그인 연장
요즘에는 대부분의 서비스가 자동 로그인을 지원합니다.
서비스를 며칠 접속하지 않았다고 해서 로그인이 풀리거나 하지 않습니다.
마찬가지로 리프레시 토큰 또한 그러한 목적으로 사용합니다.
이를 위해서는 리프레시 토큰도 재발급을 하여 그 기간을 연장해주어야 했습니다.
저는 리프레시 토큰을 도중에 새로 발급함으로써 이 문제를 해결할 수 있다고 생각했습니다.
이렇게 구현한 리프레시 토큰은 그 안에 유저 정보를 저장할 필요가 없었습니다.
리프레시 토큰을 데이터베이스에 저장한다는 것은 그 유효성 또한 데이터베이스에서만 확인할 수 있다는 의미이기 때문입니다.
리프레시 토큰은 잘 작동했습니다.
다른 블로그나 설명글을 보아도 저와 유사한 로직으로 구현하는 것을 볼 수 있었습니다.
그러나 문제가 있다
하지만 이 로직에는 몇 가지 문제점이 있었습니다.
우선 기본적으로 jwt로 구현한 토큰 로그인 방식은 stateless를 전제로 합니다.
이것이 무슨 말이냐면 jwt의 원래 목적은 상태(데이터)를 서버에 저장하지 않고 토큰 자체만으로 상태를 관리하는 용도라는 것입니다.
토큰 안에는 정보를 담을 수 있으므로 데이터베이스에 굳이 저장을 하지 않아도 됩니다.
그럼에도 리프레시 토큰을 데이터베이스에 저장한다는 것은 서버가 상태를 관리한다는 뜻이고
이는 상태 관리의 효율을 떠나 원래의 jwt 목적에 반한다는 것입니다.
게다가 액세스 토큰의 재발급만이 목적이고 데이터베이스에 저장을 할 것이라면 굳이 jwt 같은 토큰 방식을 사용할 이유가 없습니다.
또 다른 문제점은 로그인 상태를 여러 기기에서 유지할 수 없다는 점입니다.
유효한 리프레시 토큰이 한 개라면, 로그인 혹은 액세스 토큰 재발급 시에 새로운 리프레시 토큰으로 대체되기 때문에 어느 한 기기에서 로그인을 하거나 액세스 토큰을 재발급받는다면 다른 기기에서는 더 이상 리프레시 토큰을 사용할 수 없게 됩니다.
이는 보안의 측면에서 장점이 될 수 있지만 요즘 같이 여러기기를 동시에 사용하는 환경에서는 큰 불편을 가져다줄 수 있습니다.
그래서 내 선택은
우선 저는 데이터베이스에 토큰을 저장하는 방식을 버리고
정해진 기간에만 리프레시 토큰을 사용할 수 있도록 로직을 되돌릴 예정입니다.
여러 기기에서 로그인 상태를 유지할 수 없다는 점이 제게는 가장 큰 문제점으로 느껴졌기 때문입니다.
앞으로 리프레시 토큰 구현 사례를 더 찾아보려고 합니다.
토큰을 하나만 사용하는 경우,
만료기간에 따라 로직을 추가하는 경우,
아니면 토큰을 사용하지 않고 전혀 다른 방식을 선택할 수도 있을 것 같습니다.
무엇이 되었든 현재 서비스에 가장 적합한 형태의 인증인가를 적용해나가겠습니다.
마무리
이번 경험을 통해 토큰 로그인에 대해 고민해보고 직접 구현까지 해 볼 수 있었습니다.
아직 만족할만한 답을 찾진 못했지만 스스로 좀 더 성장했음을 느낄 수 있었습니다.
특히 로그인 상태 관리는 서비스의 특성에 따라 크게 달라질 수 있겠다는 생각이 들었습니다.
여러분도 지금 만들고 있는 서비스에 가장 적합한 인증인가 방식을 구현하시기 바랍니다!
'나는 이렇게 학습한다 > CS' 카테고리의 다른 글
로그인시 Access Token, Refresh Token 보내주기 (0) | 2022.04.13 |
---|---|
HTTP(Hyper Text Transfer Protocol)를 알아보자 (0) | 2022.03.21 |
TCP와 UDP 비교 (0) | 2022.03.20 |
메모리 단편화를 해결하는 세 가지 방법 (0) | 2022.02.09 |
동시성(병행성) vs 병렬성 (0) | 2022.01.16 |