JWT

윤종석·2025년 8월 14일

CS

목록 보기
8/11

JWT란?

JSON Web Token이다.

인증 방식 중에 하나로, Cookie based session management(세션 인증 방식)를 대체하기 위해 등장했다. (물론 JWT도 쿠키에 저장되기는 한다. session을 안 쓸 뿐이다.)

JWT는 서버의 무상태성을 유지하면서, 각 사용자들의 로그인 상태를 유지한다.

이 무상태성이 JWT를 사용하는 가장 큰 이유이다.

(무상태성은 Restful API 설계 원칙에도 포함된다.)

무상태성(Stateless)

각 사용자들의 로그인 상태 정보를 서버에 저장하지 않으므로, 서버의 메모리 부하를 줄일 수 있음을 의미한다.

반면에 클라이언트의 메모리(브라우저의 쿠키)에 로그인 정보가 저장되므로, 클라이언트의 메모리 부하가 소폭 증가한다. 하지만 각 클라이언트는 자신의 정보만 저장하면 되므로, 부하가 미미하다.

클라이언트에 저장되는 토큰의 payload에는 아래의 정보들이 포함된다.

  • 토큰 발급자 - 발급한 서버 도메인
  • 사용자 식별자 - 사용자 id
  • 토큰 수신 대상 - 이 토큰을 인증 용도로 사용할 서버 도메인
  • 만료 시각
  • 발급 시각
  • JWT ID

각 시각은 tick으로 표현된다.

클라이언트가 완전히 홀로 자신의 로그인 정보를 관리한다는 것인데, 그러면, 클라이언트가 위조하면 그만 아니야?

그게 되면, 쓸 리가 없다.

JWT의 핵심은 signature를 통한 위조 방지이다. 암호화를 통해 signature를 만들고, 이를 통해 위변조를 파악한다.

암호화 방식으로는 단방향 암호화, 비대칭키 양방향 암호화 모두 사용 가능한데, 일반적으로는 암호화 속도가 압도적으로 빠른 단방향 암호화(ex HS256)를 채택한다.

다만, 토큰 수신 대상(토큰을 사용하는 서버)가 여러 곳인 경우에는 RS256같은 비대칭키 양방향 암호화를 사용하기도 한다. 이때는 다른 토큰 유효성 검사 방식을 사용한다.

(signature 암호화의 핵심은 무결성이다. 기밀성은 필요없다.)

JWT의 구조

이제 JWT의 구조를 보고, 어떻게 동작하는지 알아보자.

JWT는 header, payload, signature로 구성된다.

  • header는 사용된 암호화 알고리즘 정보를 포함한다.
  • signature는 header와 payload를 base64로 인코딩한 후 합친 것을 서버의 개인키로 암호화한 문자열이다.
    • 여기서 개인키는 발급자(서버)만 알아야 한다. (유출되는 순간, 모든 인증은 무의미해진다.)
//header
{
  "alg": "HS256",
  "typ": "JWT"
}
//payload
{
  "sub": "1234567890",
  "name": "Aiden",
  "admin": true,
  "iat": 1712972900,
  "exp": 1712976500,
  "aud": "myapp.example.com",
  "iss": "auth.example.com"
}
//signature는 Base64UrlEncode(header) + "." + Base64UrlEncode(payload)를 개인키로 암호화한 문자열

header, payload, signature는 각각 base64로 인코딩된 뒤, 그 사이를 ‘.’으로 구분하여 이어붙여서 다음과 같은 한 줄의 문자열로 변환된다.

//header.payload.signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTcxMjk3MjkwMCwiZXhwIjoxNzEyOTc2NTAwLCJhdWQiOiJteWFwcC5leGFtcGxlLmNvbSIsImlzcyI6ImF1dGguZXhhbXBsZS5jb20ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

결국 클라이언트와 서버는 위 한줄의 문자열을 계속해서 주고 받고, base64로 디코딩하여, 내용을 확인한다.

서버는 자주쓰는 사용자의 정보를 payload에 담아서 캐싱하듯 사용할 수 있다. 클라이언트에서 위변조 시 서버가 바로 알아챌 수 있기 때문이다.

그렇다면, 서버는 어떻게 클라이언트의 위변조 여부를 알아챌 수 있는가?

토큰의 유효성 검사 방식은 암호화 방식에 따라 두가지 방법이 있다.

단방향 암호화 방식을 사용한 경우

가장 많이 쓰는 HS256으로 설명하겠다.

토큰 검증은 주로 서버에서 일어나므로, 서버 입장을 보자. (여기서 토큰 발급자와 토큰 사용자(검증자)는 동일하다)

서버는 클라이언트에서 토큰을 받는다. 서버는 무상태성을 유지하기 위해, 토큰에 대한 정보를 전혀 가지고 있지 않으므로, 토큰 자체에서 내가 발급한 것이 맞는지 확인해야 한다.

서버는 받은 토큰의 header와 payload를 자신의 개인키로 다시 암호화해서 토큰의 signature와 비교한다. 이 때, 완벽하게 같으면, 토큰의 유효성을 인증한다. (서로 완전히 같다는 것은, 자신이 발급한 토큰이 맞으며, 토큰 내용이 발급 시점과 아무것도 바뀌지 않았다는 뜻)

비대칭키 양방향 암호화 방식을 사용한 경우

비대칭키 양방향 암호화 알고리즘으로는 RS256을 가장 많이 쓴다. HS256에 비해 느리지만, 하나의 토큰을 여러 서버에서 사용할 때(MSA 구조), 매우 유용하다. HS256 방식을 사용하려면 각 서버가 하나의 개인키를 공유해야하는데 이 과정에서 개인키가 유출될 가능성이 있다. 이 개인키가 유출되면 클라이언트가 토큰을 마음대로 위변조할 수 있으므로, 사실상, 모든 인증 인가 시스템이 무력화된다.

RS256 방식을 사용하면 각 서버는 하나의 개인키를 공유할 필요 없이, 토큰 발급자의 공개키를 통해 검증할 있으므로, 개인키가 네트워크를 통과할 일이 없으며, 공개키로는 토큰 검증만 가능하며, 토큰 자체의 위변조는 불가능하다.

RS256 방식의 검증 방식은 조금 다르다. payload를 암호화하는 방식이 아니라, signature를 복호화한다. signature는 토큰 발급자의 공개키로 복호화한 뒤, 결과를 payload와 비교한다. 이때 서로가 완벽하게 동일하면, 토큰의 유효성을 인증한다.

profile
node로 다하고 싶어요

0개의 댓글