JWT이란?

JWT(JSON Web Token)이란?

JWT(Json Web Token)란 Json 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. JWT는 토큰 자체를 정보로 사용하는 Self-Contained 방식으로 정보를 안전하게 전달한다.

  • JSON Web Token의 약자로 전자서명 된 URL-safe(URL로 이용할 수 있는 문자로만 구성된)의 JSON 이다.
  • Signature은 JSON의 변조를 체크할 수 있다.
  • 속성 정보(Claims)를 JSON 데이터 구조로 표현한 토큰으로 RFC 7519 표준이다.
  • 서버와 클라이언트 간 정보를 주고 받을 때, HttpRequest header에 JSON 토큰을 넣은 후, 서버는 별도의 인증 과정이 없이 JWT 정보를 통해서 인증한다.
  • HMAC 알고리즘을 사용하여, 비밀키 또는 RSA를 이용한 public key/ private key 쌍으로 서명 가능하다.
  • Base64 인코딩인 경우 "+", "/", "="이 포함되나, JWT는 URL에서 파라미터로 사용할 수 있게 URL-safe한 base64url 인코딩을 사용한다.
  • JWT의 서명 JWS(JSON Web Signature), 암호화 JWE(JSON Web Encryption) 관련된 사양도 각각 RFC 7515RFC 7516에 게시되어 있다.

RFC(Request for Comments) 문서는 비평을 기다리는 문서라는 의미로, 컴퓨터 네트워크 공학 등에서 인터넷 기술에 적용 가능한 새로운 연구, 혁신, 기법 등을 아우르는 메모를 나타낸다. - 위키백과

JWT 특징

  • 다양한 프로그램 언어에서 지원된다.
    • JWT는 다양한 언어제 지원된다. C, Java, Python, C++, C#, PHP, Javascript, Ruby, Go, Swift 등등
  • 자가수용적(self-contained) 이다.
    • JWT는 필요한 모든 정보를 자체적으로 지니고 있다. 토큰에 대한 기본정보, 저달할 정보, 토큰 검증됐다는 것을 증명해 주는 signature를 포함하고 있다.
  • 쉽게 전달된다.
    • 자가수용적이므로, 두 개체간에 손쉽게 전달이 된다. 웹서버의 경우 HTTP의 헤더에 넣어서 전달 할 수도 있고, URL의 페라미터로도 전달할 수 있다.

JWT의 장단점

JWT의 장점

  • JWT 의 주요한 이점은 사용자 인증에 필요한 모든 정보는 토큰 자체에 포함하기 때문에 별도의 인증 저장소가 필요 없다.
  • 쿠키를 전달하지 않아도 되므로 쿠키를 사용함으로써 발생하는 취약점이 사라진다.
  • URL 파라미터와 헤더로 사용
  • 트래픽 대한 부담이 낮음
  • REST 서비스로 제공 가능
  • 내장된 만료
  • 독립적인 JWT

JWT 단점 및 고려사항

  • Self-contained: 토큰 자체에 정보를 담고 있으므로 양날의 검이 될 수 있다.
  • 토큰 길이: 토큰의 페이로드(Payload)에 3 종류의 클레임을 저장하기 때문에, 정보가 많아질수록 토큰의 길이가 늘어나 네트워크에 부하를 줄 수 있다.
  • Payload 인코딩: 페이로드(Payload) 자체는 암호화 된 것이 아니라, BASE64로 인코딩 된 것이다. 중간에 Payload를 탈취하여 디코딩하면 데이터를 볼 수 있으므로, JWE로 암호화하거나 Payload에 중요 데이터를 넣지 않아야 한다.
  • Stateless: JWT는 상태를 저장하지 않기 때문에 한번 만들어지면 제어가 불가능하다. 즉, 토큰을 임의로 삭제하는 것이 불가능하므로 토큰 만료 시간을 꼭 넣어주어야 한다.
  • Tore Token: 토큰은 클라이언트 측에서 관리해야 하기 때문에, 토큰을 저장해야 한다.

JWT 구조

JWT는 크게 헤더(Header), 페이로드(Payload), 서명(Signature)의 3 부분으로 구분되고, 각 부분을 점(.)으로 구분한다.

HEADER.PAYLOAD.SIGNATURE

Json 형태인 각 부분은 Base64로 인코딩 되어 표현된다. Base64는 암호화된 문자열이 아니고, 같은 문자열에 대해 항상 같은 인코딩 문자열을 반환한다.

3 부분을 합한 형식을 아래와 같다.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT 검증 사이트 https://jwt.io/ 값을 파싱 할수 있다.

헤더 (HEADER)

Header에는 토큰의 타입과 해시 암호화 알고리즘으로 구성되어 있다. JWT를 검증하는데 필요한 정보를 가진 JSON 객체는 Base64 URL-Safe 인코딩된 문자열이다.

{
  "alg": "HS256",
  "typ": "JWT"
}
예약어 의미 설명
alg Algorithm Signature 해싱 알고리즘을 지정한ㄷ. 해싱 알고리즘으로는 보통 HMAC, SHA256 혹은 RSA가 사용되며, 이 알고리즘은 토큰을 검증 할 때 사용되는 signature 부분에서 사용된다.
typ Type 토큰 타입 지정한다. JWT를 지정한다.

페이로드 (PAYLOAD)

JWT에 담아서 전달할 data를 정의한다. 토큰의 페이로드에는 토큰에서 사용할 정보의 조각들인 클레임(Claim)이 담겨 있다. Json(Key/Value) 형태로 다수의 정보를 넣을 수 있다.

페이로드 예시는 아래와 같다.

{
  "exp": 1620290493,
  "jti": "7d8b0d7b-08a3-4bad-96a4-911b8945553e",
  "http://www.devkuma.com/": true,
  "username": "araikuma",
  "authorities": [
    "ROLE_USER"
  ],
  "scope": [
    "read",
    "write"
  ]
  ... 기타 등등 ....
}

클레임의 종류는 다음과 같이 크게 세 분류로 나뉘어 진다.

등록된 클레임(Registered Claim)

등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기 위하여 이름이 이미 정해진 정보이다. 등록된 클레임의 사용은 모두 선택적 (optional)이며, 이에 포함된 클레임 이름들은 다음과 같다.

예약어 의미 설명 지정 데이터 형식
iss Issuer JWT 발급자(서버 측)의 식별자 문자열 / String Or URI
sub Subject JWT의 주어가 되는 주체의 식별자. 문자열 / String Or URI 이 값이 문자열인 경우에는 인증할 client_id여야 한다.
aud Audience JWT를 이용하는 주체(클라이언트)의 식별자 문자열 / String Or URI 값의 배열 ※ 단일도 가능
exp Expiration Time 만료 날짜. 기한 이후에 JWT의 처리는 NG 1970-01-01 00 : 00 : 00Z부터 초 단위로 수치 (IntDate)로 지정
nbf Not Before 유효 기간 시작 날짜. 이는 이전에 JWT의 처리는 NG. 1970-01-01 00 : 00 : 00Z부터 초 단위로 수치 (IntDate)로 지정
iat Issued At JWT의 발행 날짜. 1970-01-01 00 : 00 : 00Z부터 초 단위로 수치 (IntDate)로 지정
jti JWT ID JWT를위한 고유 (고유) 식별자. 중복이 일어나지 않도록 지정해야 한다. 대문자와 소문자를 구별하는 문자열
typ Type 콘텐츠 형식의 선언 대문자와 소문자를 구별하는 문자열

공개 클레임(Public Claim)

공개 클레임들은 충돌이 방지된 (collision-resistant) 이름을 가지고 있어야 한다. 충돌을 방지하기 위해서는, 클레임 이름을 URI 형식으로 짓는다.

{
  "http://www.devkuma.com/": true
}

비공개 클레임(Private Claim)

등록된 클레임도 아니고, 공개된 클레임들도 아닙니다. 양 측간에 (보통 클라이언트 <->서버) 협의하에 사용되는 클레임 이름들입니다. 공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야 한다.

{
  "username": "araikuma"
}

서명 (VERIFY SIGNATURE)

서명(Signature)은 토큰을 인코딩하거나 유효성 검증을 할 때 사용하는 고유한 암호화 코드이다. 서명(Signature)은 위에서 만든 헤더(Header)와 페이로드(Payload)의 값을 각각 BASE64로 인코딩하고, 인코딩한 값을 비밀 키를 이용해 헤더(Header)에서 정의한 알고리즘으로 해싱을 하고, 이 값을 다시 BASE64로 인코딩하여 생성한다.

HMACSHA512(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)
secret base64 encoded

JWT 인증 방식

대략적인 JWT를 사용한 인증의 흐름은 아래와 같다.

  1. 헤더 or 페이로드가 변조되지 않았는지 확인한다.
  2. 페이로드가 만료일(exp) 등에 문제가 없는지 확인한다.
  3. 문제가 없으면 인증이 완료된다.

로그인 이전

  1. 프런트 엔드에서 사용자 정보(ID, 비밀번호 등)를 요청한다.
  2. 백엔드에서 받은 ID와 암호 검증한다.
  3. 검증이 되었다면, 개인 키 JWT 발행한다. (인증 된 사용자 정보와 유효 기간을 포함한다.)
  4. 발행한 JWT를 HTTP 응답 헤더 Cookie에 담아 프런트 엔드에 반환한다.
  5. 프론트 엔드에서 base64로 변환하고 LocalStorage 등에 저장한다.
  6. 로그인이 완료된다.

로그인 이후

  1. 프런트 엔드 LocalStorage에 토큰이 있는지 확인한다.
    • 토큰이 있다면, 그 유효 기간을 확인하고 HTTP 헤더에 넣어 백엔드에 요청한다.
      • 유효 기간이 끝났다면, 백엔드에 요청하고 백엔드에서는 토큰을 갱신(새로 고침)하여 반환한다.
        • 프론트엔드에서는 처음 등록 할 때와 마찬가지로 돌아온 토큰을 LocalStorage에 저장한다.
    • 토큰이 없다면, 사용자에 따라 적절한 페이지로 리다이렉트한다.
  2. 백엔드에서 비밀 키(secret-key)를 사용하여 받은 JWT가 손상되지 않았는지 확인하고, 만료되지 않았는지 확인한다.
    • 토큰이 유효하다면, 그대로 처리를 계속 필요에 따라 프런트엔드에 데이터를 반환한다.
    • 토큰이 유효하지 않으면, 인증 오류를 프런트 엔드에 응답한다.