본문 바로가기

카테고리 없음

JWT

JWT는 Json Web Token의 약자로 토큰의 한 종류이다. JWT는 마침표를 기준으로 헤더, 페이로드, 서명으로 나뉜다.

  • 헤더(Header)
    • 암호화 알고리즘 및 토큰 타입에 대한 정보를 담고 있다.
  • 페이로드(Payload)
    • 사용자 정보, 토큰 만료 시간 등의 claim 정보를 담고 있다.
    • claim은 key-value 형태로 구성되어 있으며, 페이로드에 여러 개의 claim을 담을 수 있다.
    • 페이로드는 서명된 파트가 아니므로 누구나 디코딩하여 데이터 열람이 가능하다. 따라서 패스워드 같은 중요한 데이터는 페이로드에 담으면 안된다.
  • 서명(Signature)
    • 헤더와 페이로드를 암호화 알고리즘으로 서명한 결과이다.

JWT의 보안 문제

기존 Access Token의 한계

Access Token만 사용했을 때의 한계점은 다음과 같다.

  • Payload 값을 탈취하기 쉽다.
    • JWT를 사용하여 아무리 암호화를 잘 해놓았다고 해도, 클라이언트와 서버 사이에서 토큰을 주고받으며 통신하는 과정에 해커에게 토큰을 탈취당해, 토큰 안에 들어있는 정보를 탈취당할 수 있다.
    • 서명(Signature)값이 개발자가 지정한 암호화 알고리즘을 통해 암호화되더라도, 해커가 이를 악용하여 Payload 영역의 값을 알아낼 수 있다.
    • 따라서 Payload에는 중요한 정보(ex, 비밀번호, 주민번호)를 저장해두면 안된다.
  •  한 번 탈취당한 토큰은 만료시간 전까지 대처할 수 없다.
    • 토큰 기반 인증 방식에서 토큰은 세션과 다르게 stateless하다. 즉 서버가 상태를 보관하고 있지 않으며, 한 번 발급한 토큰에 대해 서버는 제어권을 갖고 있지 않다.
    • 따라서 JWT 토큰이 탈취되면 해커는 사용자인척 리소스에 접근할 수 있으며, 서버측에서는 토큰 시간이 만료되길 기다릴 뿐 사용자 계정의 제어권을 해커에게 내어줄 수 밖에 없다.

Refresh Token의 등장

앞서 언급한 JWT 토큰의 보안 문제를 해결하기 위해, Refresh Token과 Access Token으로 나누어 토큰을 발급하는 방식을 알아보자.

 

앞서 언급했듯이 토큰 방식은 stateless 하기 때문에 탈취당하면 서버측에서는 토큰 만료 시간을 기다리는 것 외에는 별다른 대응을 할 수 없다. 따라서 탈취 위험을 낮추기 위해 토큰 만료 시간을 짧게 설정하면 된다. 그러나 만료 시간을 짧게 설정하면 사용자는 빈번하게 재로그인을 해야하므로 번거롭다.

 

따라서 사용자 검증을 위한 용도인 Access Token과 Access Token을 재발급하는 용도인 Refresh Token을 발급하고, 이때 Access Token의 유효 기간은 짧게, Refresh Token의 유효 기간은 길게 설정하면 된다. 즉 Access Token의 유효 기간을 짧게 설정하여 토큰 탈취의 위험을 낮추고, Refresh Token을 통한 Access Token 재발급을 통해 유효 기간이 짧은 Access Token이 만료되었을 때 사용자가 재로그인하는 번거로움을 줄일 수 있다.

* Access Token: resource에 접근할 수 있는 필수 정보를 담은 토큰으로, 서버는 Access Token을 통해 클라이언트를 식별하고 권한을 준다. 유효 기간을 짧게 설정하여, 토큰 탈취의 위험성을 낮춘다.

* Refresh Token: 새로운 Access Token을 발급받기 위한 토큰으로, Access Token 보다 유효 기간을 길게 설정하여, Access Token이 만료됐을 때 Refresh Token을 통해 Access Token을 재발급한다. 따라서 유효 기간이 짧은 Access Token으로 인한 빈번한 재로그인이 일어나지 않게 한다.

RTR(Refresh Token Rotation)

Access Token 탈취의 위험성을 낮추기 위해 Refresh Token을 활용한다. 그런데 Refresh Token이 탈취당하면 어떻게 될까? 해커는 탈취한 Refresh Token으로 Access Token을 발급받아 사용자인척 리소스에 접근할 수 있다. Refresh Token 탈취 위험을 낮추기 위한 방법으로 RTR(Refresh Token Rotaion)에 대해 알아보자. RTR은 Refresh Token을 통해 Access Token을 재발급받을 때 Refresh Token도 재발급하는 방법이다. 즉 Refresh Token을 한 번만 사용하는 방법이다.

  1. 예를 들어 최초 로그인시 서버는 Access Token과 Refresh Token을 사용자에게 발급해준다. 그리고 이때 Refresh Token을 사용자 DB에 저장해둔다.
  2. Access Token이 만료되어 사용자는 Refresh Token을 전달하며 새로운 Access Token 발급을 요청한다.
  3. 서버는 전달받은 Refresh Token 자체의 유효성 검증과 함께 DB에 저장된 사용자의 Refresh Token과 전달받은 Refresh Token을 비교한다.
    1. 전달받은 Refresh Token이 만료된 경우, 재로그인을 알린다.
    2. 반면 전달받은 Refresh Token이 유효하고 사용자 DB의 Refresh Token과도 일치하면, 새로운 Access Token과 Refresh Token을 발급한다. 그리고 DB에 저장된 Refresh Token 값을 재발급된 Refresh Token 값으로 변경한다.

RTR 방법을 적용하면 다음과 같은 장점이 있다. 향후 해커가 Refresh Token을 탈취해서 Access Token 재발급을 요청할 경우, 전달 받은 Refresh Token의 유효성 검사 뿐만 아니라 DB에 저장되어 있던 Refresh Token과의 비교까지도 진행된다. 따라서 DB의 Refresh Token과 일치하지 않을 경우(즉 가장 최신의 Refresh Token이 아닌 이전 버전의 Refresh Token인 경우) Access Token 발급이 이뤄지지 않는다. 즉 Refresh Token이 만료되기 까지 기다리는 방법 외에도 "최근 Refresh Token이냐 탈취된 이전 버전의 Refresh Token이냐"의 검증까지 이뤄지기 때문에 좀 더 보안상 안전하다고 할 수 있다.

Refresh Token을 DB에 저장하면, Stateless 하다는 장점이 사라지는 것이 아닌가?

➡️ 어느정도는 맞다. 하지만 매 요청마다 Access Token이 서버측에 전달되어야 하는 것과 달리, DB에 저장된 Refresh Token에 접근하는 일은 Access Token이 만료되어 재발급하는 경우에만 일어난다. 따라서 전체를 Stateful 하게 가져가는 것 보다는 가볍게 운영이 가능하다.

사용 흐름

  1. 사용자가 로그인을 한다.
  2. 서버는 입력받은 값과 회원 DB의 값을 비교하여 사용자를 확인한다.
  3. 올바른 사용자라면, Access Token과 Refresh Token을 발급한다. 그리고 이때 일반적으로 Refresh Token을 회원 DB에 저장해둔다.
  4. 사용자는 Refresh Token은 안전한 저장소에 저장해둔다.
  5. 사용자는 이후에 로그인이 필요한 리소스에 접근할 때마다 Access Token을 헤더에 실어 요청한다.
  6. 서버는 전달 받은 Access Token을 검증한다.
  7. 전달받은 Access Token이 올바른 토큰인 경우 리소스 접근을 허가하고 알맞은 응답을 보낸다.
  8. 시간이 지나 Access Token이 만료되었다고 가정하자.
  9. 사용자가 이전과 동일하게 Access Token을 요청 헤더에 실어 요청한다.
  10. 서버는 Access Token이 만료됨을 확인하고 알맞은 응답을 보낸다.
  11. 사용자는 Access Token 만료 응답을 받는다.
  12. 사용자는 Refresh Token을 통해 Access Token 재발급 요청을 보낸다.
  13. 서버는 전달 받은 Refresh Token 자체가 유효한지, 그리고 사용자 DB에 저장된 Refresh Token와 동일한지 비교한다.
    1. Refresh Token이 만료된 경우, 재로그인을 알린다.
    2. 반면 전달받은 Refresh Token이 유효하고 사용자 DB의 Refresh Token과도 일치하면, 새로운 Access Token과 Refresh Token을 발급한다. 그리고 DB에 저장된 Refresh Token 값을 재발급된 Refresh Token 값으로 변경한다. (RTR 방식)

JWT 보안의 한계

JWT 보안을 위해 Refresh Token과 RTR 방식을 알아봤다. 그러나 그럼에도 JWT는 여전히 다음과 같은 보안상 한계점을 갖는다.

  • Access Token을 즉시 차단할 방법의 부재
    • 아무리 Refresh Token을 통해 Access Token의 유효 기간을 짧게 설정한다고 하더라도, 그 짧은 유효 기간 내에 Access Token을 탈취하여 악용할 수 있다.
    • 토큰 방식은 stateless 하기 때문에 서버측에서는 Access Token이 만료되길 기다리는 방법 밖에 없다.
  • Refresh Token 자체를 탈취 당할 가능성
    • Refresh Token을 탈취 당하면 해커는 Refresh Token을 통해 Access Token을 발급받아 악용할 수 있다.
    • RTR 방법을 사용해 Refresh Token을 한 번만 사용하고 버린다면 조금은 더 안전할 수는 있지만, 사용하지 않은 Refresh Token을 탈취 당한다면 해커는 1회 한정으로 Access Token을 발급받을 수 있다.
    • 즉 어떻게 대응하나 Refresh Token이 탈취 당하면 보안상 대응할 뿐 위험에 불가피하다. 따라서 클라이언트는 Refresh Token이 탈취되지 않도록 안전하게 보관해야 한다.

Reference