JWT 토큰 인증

서해빈·2021년 4월 6일

JWT

API 서비스를 제공하는 서버는 다른 클라이언트들이 해당 서비스의 데이터를 가져갈 수 있게 해야 하는 만큼 별도의 인증 과정이 필요하다. JWT 토큰은 이를 위한 인증 방법 중 하나이다.

JWT는 JSON Web Token의 약어로, JSON 형식의 데이터를 저장하는 토큰이다. JWT는 헤더, 페이로드, 시그니처로 구성되어 있다.

  • 헤더(Header): 토큰 종류와 해시 알고리즘 정보가 들어 있다.
  • 페이로드(Payload): 토큰의 내용물이 인코딩된 부분이다.
  • 시그니처(Signature): 일련의 문자열로, 시그니처를 통해 토큰이 변조되었는지 여부를 확인할 수 있다.

시그니처는 JWT 비밀키로 만들어진다. 이 비밀키가 노출되면 JWT 토큰을 위조할 수 있으므로 비밀키를 철저히 숨겨야 한다. 시그니처 자체는 숨기지 않아도 된다.

JWT는 내용을 볼 수 있기 때문에 민감한 내용을 넣어서는 안된다.
그렇다면 내용이 노출되는 토큰을 왜 사용할까? 모순적이지만, 내용이 들어 있기 때문이다. 만약 내용이 없는 랜덤한 토큰이라면 토큰의 주인이 누구인지, 그 사람의 권한은 무엇인지 매 요청마다 확인해야 한다. 이러한 작업은 보통 오버헤드가 큰 데이터베이스 조회 작업을 수반한다.

JWT 비밀키를 알지 않는 이상, JWT 토큰은 변조가 되더라도 시그니처를 검사할 때 들통나기 때문에 내용물이 바뀌지 않았는지 걱정할 필요가 없다. 따라서 사용자 이름이나 권한 등 외부에 노출되어도 괜찮은 정보들을 넣고 사용해도 좋다.

단점은 용량이 크다는 것이다. 내용물이 들어 있기 때문에 랜덤한 토큰에 비해 용량이 크다. 매 요청 시 토큰이 오고 가기 때문에 데이터 양이 증가한다. 따라서 정보 조회와 큰 용량 중 덜 부담되는 쪽을 사용하면 좋다.

JWT 심화

구성

헤더는 JWT 토큰을 어떻게 해석해야 하는지를 명시하는 부분이다. 이 헤더를 열어보면 Payload와 Signature를 어떻게 해석할지를 알 수 있다. 헤더의 값이 {"typ":"JWT","alg":"HS256"}라고 한다면 이를 base64로 인코딩해서 헤더를 생성한다. 위 그림에서 토큰에 사용한 알고리즘은 HS256 즉, HMAC SHA-256이라는 의미이다. HS256 외에도 HS512, RS256(RSASSA SHA-256), ES256(ECDSA P-256 curve SHA-256) 등의 알고리즘을 사용할 수 있다.

Payload

실제 토큰의 바디에 포함할 내용을 넣는다. JWT는 토큰을 디코딩해서 바로 값을 확인할 수 있는 구조로 되어 있어서 데이터베이스 조회할 필요없이 사용자 아이디 등을 여기에 담아서 바로 사용할 수 있다. 토큰의 내용이 {"iss":"John Doe","exp":1434290400000,"username":"john","age":25,"iat":1434286842654}라고 한다면 헤더와 같게 base64 인코딩을 한다.

Signature

앞에서 보았듯이 헤더와 페이로드는 암호화를 한 것이 아니라 단순히 JSON문자열을 base64로 인코딩한 것뿐이다. 그래서 누구나 이 값을 다시 디코딩하면 JSON에 어떤 내용이 들어있는지 확인할 수 있다. 토큰을 사용하는 경우 이 토큰을 다른 사람이 위변조할 수 없어야 하므로, 헤더와 페이로드의 위변조 여부를 검증하기 위한 부분이 Signature 부분이다. 헤더와 페이로드를 base64로 인코딩해서 만든 두 값을 마침표(.)로 이어 붙이고 헤더에서 alg로 지정한 알고리즘 HS256 즉, HMAC SHA-256으로 인코딩하면 JWT 토큰의 세 번째 부분인 Signature가 완성된다.
공격자가 서명을 위변조할 수 없도록 비밀키를 사용해서 서명하는데, 당연히 이 비밀키는 외부에 노출되면 안 된다.

JWT의 사용

JWT가 다른 토큰하고 가장 다른 부분은 토큰 자체가 데이터를 가지고 있다는 점이다. 일반적으로 토큰 기반의 인증을 구현한다면 API 요청 시 헤더나 파라미터에 엑세스토큰을 가져오도록 하고 이 토큰을 보고 인증한다.

  • 일반적인 토큰의 흐름
    API 요청 시에 들어온 토큰을 보고 이 토큰이 유효한지 확인하게 된다. 보통은 데이터베이스에 토큰과 만료 시간, 사용자 등을 저장해 놓고 조회를 통해 유효성 검사를 수행한다. 이 후 해당 사용자라고 인식하면 이 사용자의 권한으로 사용할 수 있는 정보를 조회하게 된다. 요청마다 데이터베이스를 조회하는 것은 비용이 꽤 크므로 캐시서버 등을 두어 성능을 높이기도 한다.

  • JWT의 경우
    토큰을 받아서 서명으로 유효한 토큰인지 검증한다. 이 후 유효하다고 판단하면 페이로드를 디코딩해서 토큰에 담긴 데이터를 열어본다. 앞에서 보았듯이 페이로드의 JSON에 만료시간 등이 담겨있으므로 토큰이 사용가능한지 검사를 하고 이상이 없으면 바로 사용한다. 토큰의 사용자 아이디 등이 담겨있다면 데이터베이스나 캐시를 조회할 필요없이 바로 애플레이케션에서 사용자를 확인하고 정보를 조회할 수 있다. 대신 다른 토큰보다 길이가 좀 길다는 문제는 있다.

이 차이는 서버 개발 쪽에서는 상당히 큰 차이를 만들어내는데 토큰만 가지고 유효성 검사나 사용자 식별까지 할 수 있으므로 서버를 무상태로 유지할 수 있다는 점이다. 데이터베이스나 캐시 없이 로직만으로 인증이 가능하므로 애플리케이션 서버를 필요에 따라 늘릴 수 있다.

주의할 점

  • 앞에서 보았듯이 페이로드은 암호화하지 않는다. 그래서 서명 없이도 누구나 열어볼 수 있기 때문에 여기에는 보안이 중요한 데이터는 넣으면 안 된다. base64로 인코딩해서 사용하다 보면 이 부분을 간과하기 쉬운데 필요한 최소한의 정보만 페이로드에 담아야 한다.
  • 인코딩 특성상 페이로드의 내용이 많아지면 토큰의 길이도 같이 길어진다. 그래서 편하다고 너무 많은 정보를 담으면 안 된다. 앞에서 정의된 페이로드의 key를 전부 약자를 사용하는 이유도 JWT를 최대한 짧게 만들기 위함이다.
  • 토큰을 강제로 만료시킬 방법이 없다. 서버가 토큰의 상태를 가지고 있지 않고 토큰 발급 시 해당 토큰이 유효한 조건이 결정되므로 클라이언트가 로그아웃하더라도 해당 토큰을 클라이언트가 제거하는 것뿐이지 토큰 자체가 만료되는 것은 아니다. 만약 이때 누군가 토큰을 탈취한다면 해당 토큰을 만료시간까지는 유효하게 된다.

cf) HMAC-SHA-256

참고 및 출처

조현영, Node.js 교과서, 길벗(2020)
JWT(JSON Web Token)에 대해서... - 바로가기

0개의 댓글