JWT와 유효 기간
이 글은 Session과 JWT에 대한 기본적인 이해를 바탕으로 작성하였습니다.
JWT(JSON Web Token)
요즘 MSA나 K8s를 기반으로 하는 서비스가 많아지면서 JWT를 정말 많이 사용하는 것 같다. 대학교(정보보안학과)에서는 Session만 배웠는데 백엔드 개발자가 되기 위해 참여한 부트캠프에서 당연하다는 듯이 JWT를 사용했고, 인증 절차를 구현하고자 검색을 하면 JWT로 구현한 블로그가 다수를 차지한다. 그 좋은 Session을 놔두고 왜 JWT를 사용하는 것일까?
도입 이유
인증 정보를 Client에 저장하기 위해서이다.
만약 K8s로 서비스를 배포한다고 가정하자. 동일한 역할을 수행하는 다수의 Server를 Container로 올리고, Load Balancer가 들어오는 요청을 각 Container로 알맞게 분배한다. 여기에서 인증 기능이 있다고 하면, Client가 인증을 완료하고 다음 요청을 보낼 때 인증 정보를 가지고 있지 않은 Server에 요청을 보내면 Server는 인증 여부를 확인할 수 없으므로 인증되지 않은 요청으로 처리한다.
이 문제를 해결하기 위해 인증 정보를 Server가 아닌, Client에 저장하는 것이다. Server는 Client가 보낸 JWT를 우리가 가지고 있는 Key로 암호화 한 것이 맞는지 확인하고 정상 여부를 확인하는 것이다.
덤으로 Server는 인증 정보를 가지고 있지 않아도 되므로 고정적으로 소모하는 자원을 절약할 수 있다.
문제점
하지만 당연히 인증 정보를 Client에게 넘겨줬기 때문에 문제가 발생한다. 한 번 넘겨준 JWT는 Server의 손을 떠나버리기 때문이다. 인증을 한 번 하고 JWT를 넘겨 받으면 이후로는 인증을 할 필요가 없는 것이다.
인증의 근간을 흔드는 이 문제점은 유효 기간을 설정함으로써 일부 해결하고 있다. 유효 기간이 만료된 JWT는 거부하고, 다시 인증 절차를 진행할 것을 요구하는 것이다. 그렇다면, 유효 기간은 어느 정도가 적당할까?
유효 기간과 Refresh Token
JWT로 인증을 구현한 많은 블로그에서 유효 기간이 긴 JWT의 탈취 문제를 해결하기 위해 Access Token과 Refresh Token, 이렇게 두 개의 JWT를 발급하는 것으로 문제를 해결한다고 주장한다. Access Token의 유효 기간을 매우 짧게 하고, 만료되면 유효 기간이 긴 Refresh Token을 같이 보내서 다시 새 Access Token을 발급받는 것이다.
아니, 뭔가 논리가 심각하게 잘못되고 있는 것이 아닌가?
1. JWT의 탈취
먼저 JWT가 탈취된다는 가정 부터가 심상치 않다. JWT는 어떤 경우에 탈취당할 수 있을까? 매 요청마다 JWT를 같이 보내니 중간에 누가 가로챌 수 있는 것일까?
아니, 정상적인 웹 서비스라면 일단 그런 일은 없다.
현대 웹 서비스에서 인증이 필요한 서비스의 거의 전부는 TLS로 암호화하여 통신하고 있다. 주소 창에서 볼 수 있는 https가 그것이다.
TLS는 종단간 암호화 기술이다. Server에서 정상적인 인증서를 가지고 있고, Client에서 https로 접근한다면 그 Client와 Server 사이의 어떤 장비가 패킷을 훔치더라도 내용을 확인할 수 없다. DNS를 포함하여 완전하게 Client의 네트워크를 장악한 중간자가 공격하지 않는 이상, 또는 TLS 자체에 심각한 취약점이 발생하지 않는 이상 일어날 수 없는 일이다. 그리고 그렇게 종단간 암호화가 무력화 되었다면, 인증이고 뭐고 더 이상 JWT가 문제가 아니다.
그렇다면 JWT가 Client에 보관되어 있으니 Client에서는 탈취당할 수 있지 않을까?
Client에서 탈취당한다면 사실상 물리적으로든 원격으로든 Client의 통제권 자체가 공격자에게 넘어갔다는 것을 의미한다. 공격자가 Client를 마음대로 할 수 있는데 안전한 것이 존재는 하는가? 마찬가지로 JWT고 뭐고 이미 끝장난 Client이다.
2. Refresh Token
아무튼 그래서 Refresh Token이 필요하다고 한다. Access Token이 탈취당하더라도 금방 만료되고 공격자는 더 이상 접근할 수 없으니까!
있을 수 없는 일이기는 해도 일단 탈취가 되었다고 가정을 해보겠다. 통신 중에 중간자가 가로채고, Client에서 공격자가 가로챈다. 그런데, 왜 Access Token은 가로채는데 Refresh Token은 가로채지 못한다고 생각하는가? Refresh Token은 어디 땅에서 솟아나는 것이 아니고, 따로 아공간 창고에 보관하는 것이 아니다. Refresh Token은 Access Token과 똑같이 관리한다.
그렇기 때문에 Refresh Token으로 유효 기간을 늘린다는 아이디어는, Access Token의 유효 기간을 늘리려고 했던 Refresh Token의 유효기간으로 만들면 되는 불필요한 작업이다. Refresh Token 자체가 의미가 없다.
3. Server 측 인증 상태 저장
만약 PC방에 가서 내 계정에 로그인을 했다고 하자. Server는 PC방 컴퓨터에게 JWT를 발급했고, 로그아웃(JWT 폐기)을 하지 않고 집으로 돌아왔다. 전형적인 Client에서 탈취당할 수 있는 사용자 부주의의 경우이다. 이 경우에 PC방에 다시 가지 않고 PC방에서 발급받은 JWT를 무효화할 수 있는 방법이 있을까? JWT는 이미 Server의 손을 떠났다.
여기에서 이제 발급한 JWT를 Server에 저장한다는 아이디어가 나온다. JWT의 장점으로 MSA와 같은 구조가 파생되므로, Redis와 같은 Server를 다른 Container에서 접근 가능하게 하고, 해당 Redis Server에 발급한 JWT의 정보를 저장하여 Server가 원할 때 Redis에서 정보를 삭제해 만료된 JWT라는 것을 알 수 있게 하는 것이다. 집에 돌아와서 “로그인 된 모든 곳에서 로그아웃” 과 같은 기능을 사용하면 되는 것이다. Server는 해당 아이디로 로그인 된 모든 JWT를 삭제할 것이다.
뭔가 심각하게 잘못됐다.
위에서 JWT의 장점으로 Server의 자원을 절약할 수 있다고 하였다. 하지만 이 과정을 추가함으로써, Server에 인증 정보를 저장하는 Session과 구조가 거의 동일해지면서 JWT의 존재 의미가 희석된다. 이제 과연 무엇을 위해서 JWT를 도입해야 한다고 할 수 있을까?
결론
어쩌면 사소한 장점 및 단점으로 JWT를 사용하기도 하고, 또 Session을 사용하기도 할 것이다. 또한 내가 아직 모르는 정보가 있을지도 모르겠다. 하지만 지금 내 생각으로는 일반적인 서비스라면 Server에서 완전히 관리할 수 있는 Session을 포기하면서 굳이 JWT를 도입해야 할 어떠한 이유는 보이지 않는다.