RFC 7519에서 말하는 JWT 스펙은 Access Token이고, Refresh Token은 엄밀히 말하여 JWT와 관계 없는 RFC 6479의 OAuth 2.0 스펙이라고 말할 수 있습니다. 공식 스펙에도 없는 Refresh Token을 구현하여 서버에서 관리하게 된다면 무상태성(Stateless)까지 어기게 되는데, 서비스에 꼭 적용시켜야만 하는 걸까요?
💡Refresh Token이 없을 경우
클라이언트는 인가(Authorization)가 필요한 요청을 할 때, 서버로부터 발급받은 Access Token을 Authorization header에 넣고 요청을 합니다. 서버 측에서는 이렇게 받은 Token의 Signature 및 Payload의 Claim을 갖고 인가를 진행하게 됩니다.
중요한 것은 Stateless인 JWT는 이 인가 과정에서의 "탈취당한 토큰"에 대한 대처 방안이 없습니다. 세션 방식이었다면 탈취된 세션에 대해 무효화 처리를 할 수 있겠지만, JWT 방식에서는 탈취되었다고 판단하더라도 이미 인증된 Token이기 때문에 그대로 응답을 해주게 됩니다.
그렇기 때문에 탈취 피해를 최소화하기 위해 Access Token의 만료시간을 짧게 설정하게 됩니다만, 이 짧은 시간 안에도 탈취를 당한다면 똑같은 상황이 발생하게 됩니다.
더욱 중요한 것은 클라이언트는 짧은 만료시간만큼 자주 인증 절차를 진행해 줘야 한다는 것입니다. 클라이언트가 Access Token 만료 1분 전에 글을 작성하다가, Token이 만료됐을 시점인 2분 후에 글쓰기 완료 요청을 보낸다면 서버에서는 인증을 요구하게 되고 작성글은 모두 사라지게 될 것입니다. 이러한 현상은 Access Token의 만료시간이 짧을수록 자주 발생하게 되겠죠.
💡그냥 Access Token으로 갱신하면 안 되나요?
Access Token은 클라이언트가 사용할 수 있어야합니다. 그런데, 클라이언트가 사용할 수 있다는 것은 곧 XSS에 취약하다는 뜻입니다. Refresh Token을 따로 두어 httpOnly 옵션을 적용하여 스크립트로 접근할 수 없게 막을 수 있습니다.
💡그리하여, Refresh Token이 필요했습니다
Refresh Token은 오직 Access Token을 "짧은 주기"로 재발급 받기 위해 존재합니다. 클라이언트 측에서 Refresh Token을 갖고 있다면, 짧은 주기로 Silent Refresh를 통해 클라이언트의 인증 없이 Access Token을 재발급 받을 수 있게 됩니다.
덕분에 Access Token의 만료시간을 짧게 가져가도 클라이언트 측에서 인증을 자주 해야 하는 불편함이 줄어들었지만, 여전히 짧은 시간 안에라도 탈취 당했을 때 대처 방안이 존재하지 않습니다.
💡그래서, 어디에서 관리하나요?
JWT를 사용하는 여러 이유 중 가장 핵심은 "무상태성(Stateless)"입니다. 즉, 서버에 상태 정보를 저장하지 않게 되므로, 확장성과 부하 분산을 개선할 수 있기 때문입니다.
그렇다면 Refresh Token은 당연히 클라이언트 측에서 관리되어야 하는 게 아닐까요?
하지만 저는 다음과 같은 이유들로 서버에서 관리하는 방법을 택했습니다.
보안 강화: 클라이언트 측에서 토큰을 저장하는 경우, 브라우저의 저장소에 저장되므로 서버에 비해 탈취될 가능성이 높아집니다.
동시 로그인 제한: 한 사용자 계정으로 동시에 여러 장치에서 로그인하거나 토큰을 사용하는 것을 제한하는 기능을 구현할 수 있습니다. 동시 로그인 횟수를 제한하거나 최근에 로그인한 장치를 기억하여 동시 로그인을 감지할 수 있습니다.
서버에서 Refresh Token을 관리한다는 것은 곧 무상태성을 지키지 않는다는 뜻입니다. 그렇지만 Stateful한 Refresh Token 관리 방식일지언정 관리하는 데이터 양에 있어서 세션 방식보다 적을 거라 생각됩니다. 즉 Stateless 방식의 경제적인 이점과 Stateful 방식의 보안을 모두 챙기는 것이죠.
💡그런데, 서버의 Refresh Token을 탈취당하면요?
그럼에도 불구하고, 여전히 Refresh Token의 탈취 이후 문제는 남아있습니다.
아예 만료시킬 Access Token 자체를 서버에서 Blacklist로 관리할 수도 있습니다.
그러나 Refresh Token을 서버에서 관리하는 것도 사실 불만족스러운데, 서버에서의 이러한 상태 관리가 Session과 다를 바가 있을까요? 그렇다면 덜 자주 사용되는 Refresh Token만 Blacklist로 관리하면 어떨까 싶습니다.
비슷한 방법 중 하나인 RTR(Refresh Token Rotation) 방식이 있습니다. Refresh Token을 통한 Access Token 발급 시 기존의 Refresh Token은 재사용할 수 없게 하고, 새로운 Refresh Token을 발급합니다. 하지만 여전히 사용되지 않은 Refresh Token을 탈취당했을 때의 대처 방안은 없습니다.
Board 서버에서는 로그인이나 Refresh Token을 통한 재발급 요청 시 기존의 Refresh Token을 삭제 후 재발급 합니다. Access Token에 대한 Blacklist 관리는 너무 Session 방식식 같았기 때문에 "그나마 조금 더 Stateless스럽기 위해" 적용시키지 않았습니다.
🎯정리
Access Token은 리소스 접근 목적으로 사용, Refresh Token은 Access Token 재발급 목적으로 사용합니다.
Refresh Token을 서버에서 관리하게 되면 브라우저에서의 관리에 비해 보안적으로 뛰어나고, 특히나 동시 로그인을 제한할 수 있습니다.
조금 더 보안에 엄격하다면 Refresh Token에 대해 RTR 정책을 적용하거나, Access Token에 대해 Blacklist 정책을 적용할 수 있습니다.