JWT
어떤 주체에 대한 주장을 모아 한 문자열에 담아 건네는 방식입니다. 점으로 갈린 조각들로 되어 있고, 자리가 빠듯한 곳에 실으려고 일부러 짧게 만들었습니다. 담긴 주장은 서명을 붙이거나 통째로 암호로 감싸서 지킵니다.
상세
JWT(JSON Web Token, JSON 웹 토큰)는 두 당사자 사이에 옮길 클레임을 나타내는 조밀하고 URL 에 안전한 수단입니다. RFC(Request for Comments) 7519 의 Abstract 가 그렇게 적습니다. 조밀함을 노린 자리도 정해 놓았습니다. HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) Authorization 헤더와 URI(Uniform Resource Identifier, 통합 자원 식별자) 쿼리 파라미터처럼 공간에 제약이 있는 환경입니다.
클레임은 어떤 주체에 대해 주장된 정보 한 조각입니다. 클레임 이름과 클레임 값의 쌍으로 적습니다. 클레임 이름은 언제나 문자열입니다. 클레임 값은 어떤 JSON(JavaScript Object Notation) 값이어도 됩니다. 이 클레임들을 담은 JSON 객체가 JWT 클레임 세트입니다.
클레임 세트만으로는 JWT 가 되지 않습니다. 그 JSON 객체를 JWS(JSON Web Signature, JSON 웹 서명) 구조 안에 넣거나 JWE(JSON Web Encryption, JSON 웹 암호화) 구조 안에 넣어야 합니다. JWS 에 넣으면 클레임에 디지털 서명이 붙거나 MAC(Message Authentication Code, 메시지 인증 코드)이 붙고, 클레임 세트가 JWS 페이로드가 됩니다. JWE 에 넣으면 클레임이 암호화되고, 클레임 세트가 JWE 가 암호화하는 평문이 됩니다. 둘 중 어느 쪽인지는 JOSE(JavaScript Object Signing and Encryption) 헤더가 정합니다. 이 헤더가 클레임 세트에 가한 암호 연산을 서술합니다.
겉모양은 점(.)으로 갈린 URL 에 안전한 조각들의 나열입니다. 조각마다 base64url 로
인코딩한 값이 들어 있습니다. 조각이 몇 개인지는 고정이 아닙니다. 결과물을 JWS 컴팩트
직렬화로 적었는지 JWE 컴팩트 직렬화로 적었는지에 달려 있습니다. JWT 는 언제나 이 두 컴팩트
직렬화 중 하나로 표현됩니다.
클레임 이름은 세 부류로 갈립니다. 등록 클레임 이름, 공개 클레임 이름, 사적 클레임 이름입니다. 등록 클레임 이름은 IANA(Internet Assigned Numbers Authority, 인터넷 할당 번호 관리기관)의 「JSON Web Token Claims」 레지스트리에 등록된 이름입니다. 충돌을 막기 위해, 새 클레임 이름은 그 레지스트리에 등록하거나 충돌 저항성 있는 이름을 담은 공개 이름으로 하라고 적혀 있습니다. 그렇게 하지 않고 만드는 쪽과 읽는 쪽이 합의해 쓰기로 한 이름이 사적 클레임 이름입니다. 사적 클레임 이름은 충돌의 대상이 되므로 주의해서 써야 한다고 적습니다.
읽는 법도 명세에 적혀 있습니다. 영어 낱말 "jot" 과 같게 발음하기를 권합니다.
출처 문서
정본은 RFC 7519, 제목은 「JSON Web Token (JWT)」입니다. IETF(Internet Engineering Task Force, 국제 인터넷 표준화 기구)가 2015년 5월에 냈습니다. 헤더의 Category 는 Standards Track 이고 ISSN(International Standard Serial Number, 국제 표준 연속 간행물 번호)은 2070-1721 입니다. Status of This Memo 절은 이 문서가 Internet Standards Track 문서이고, IETF 공동체의 합의를 나타내며, 공개 검토를 거쳐 IESG(Internet Engineering Steering Group, 인터넷 기술 운영 그룹)의 출판 승인을 받았다고 적습니다. 저자는 Microsoft 의 M. Jones, Ping Identity 의 J. Bradley, NRI 의 N. Sakimura 입니다.
이 문서를 갱신하는 문서가 하나 있습니다. RFC 8725, 「JSON Web Token Best Current
Practices」입니다. 2020년 2월에 나왔고 BCP(Best Current Practice) 225 번을 받았으며
헤더에 Updates: 7519 가 적혀 있습니다. RFC 7519 를 폐기하는 문서가 아닙니다. 안전한
구현과 배치로 이어지는 실행 가능한 지침을 주려고 RFC 7519 를 갱신하는 문서입니다.
요구 강도를 읽는 법
§1.1 Notational Conventions 는 MUST · MUST NOT · REQUIRED · SHALL · SHALL NOT · SHOULD · SHOULD NOT · RECOMMENDED · NOT RECOMMENDED · MAY · OPTIONAL 을 RFC 2119 가 서술한 대로 해석하라고 적습니다. 그리고 그 해석은 낱말이 전부 대문자로 나타날 때만 적용된다고 덧붙입니다. 본문에 소문자 "should" 가 섞여 있다면 그것은 요구 강도가 아닙니다. RFC 7519 본문이 요구 강도 낱말의 근거로 인용하는 문서는 RFC 2119 하나입니다.
등록 클레임 이름
§4.1 이 클레임 이름 일곱 개를 IANA 레지스트리에 등록합니다. 다만 여기 정의된 클레임 가운데 어느 것도 모든 경우에 쓰거나 구현하는 것이 의무이도록 의도되지 않았다고 §4.1 이 먼저 못 박습니다. 쓸모 있고 상호운용 가능한 클레임 묶음의 출발점을 주는 것이고, 어느 클레임을 쓰고 언제 필수이고 언제 선택인지는 JWT 를 쓰는 애플리케이션이 정하라고 적습니다. 이름이 전부 짧은 것은 표현을 조밀하게 만드는 것이 JWT 의 핵심 목표이기 때문입니다.
| 클레임 | 절 | 무엇을 가리키나 | 값의 꼴 | 요구 강도 |
|---|---|---|---|---|
iss |
§4.1.1 | JWT 를 발급한 주체 | 대소문자를 구분하는 StringOrURI | 이 클레임의 사용은 OPTIONAL |
sub |
§4.1.2 | JWT 의 주체 | 대소문자를 구분하는 StringOrURI | 사용은 OPTIONAL. 값은 발급자 문맥에서 지역적으로 유일하거나 전역적으로 유일하도록 범위가 잡혀야 한다(MUST) |
aud |
§4.1.3 | 이 JWT 를 받도록 의도된 수신자들 | 일반적으로는 StringOrURI 를 담은 대소문자 구분 문자열의 배열. 수신자가 하나뿐인 특수한 경우에는 문자열 하나여도 된다(MAY) | 사용은 OPTIONAL. 처리하려는 주체는 자신을 이 값들 중 하나로 식별해야 하고(MUST), 클레임이 있는데 식별하지 못하면 JWT 를 거부해야 한다(MUST) |
exp |
§4.1.4 | 이 시각 이후로는 처리에 받아들이면 안 되는 만료 시각 | NumericDate 를 담은 수여야 한다(MUST) | 사용은 OPTIONAL. 현재 시각이 만료 시각보다 앞이어야 한다(MUST) |
nbf |
§4.1.5 | 이 시각 전에는 처리에 받아들이면 안 되는 시각 | NumericDate 를 담은 수여야 한다(MUST) | 사용은 OPTIONAL. 현재 시각이 그 시각 이후이거나 같아야 한다(MUST) |
iat |
§4.1.6 | JWT 가 발급된 시각 | NumericDate 를 담은 수여야 한다(MUST) | 이 클레임의 사용은 OPTIONAL |
jti |
§4.1.7 | JWT 의 유일한 식별자 | 대소문자를 구분하는 문자열 | 이 클레임의 사용은 OPTIONAL |
exp 와 nbf 는 둘 다 시계 어긋남을 감안한 여유를 허용합니다. 구현자는 약간의 여유를 둘
수 있고(MAY), 그 여유는 보통 몇 분을 넘지 않습니다. jti 의 값은 같은 값이 다른 데이터
객체에 우연히 배정될 확률이 무시할 만하도록 배정되어야 합니다(MUST). 애플리케이션이 발급자를
여럿 쓴다면 서로 다른 발급자가 만든 값 사이에서도 충돌을 막아야 합니다(MUST). 이 클레임은
JWT 가 재생되는 것을 막는 데 쓸 수 있다고 적혀 있습니다.
iss 와 sub 와 aud 는 값의 해석이 일반적으로 애플리케이션마다 다르다고 §4.1 이
적습니다. 값의 꼴에 쓰인 두 낱말은 §2 가 정의합니다. StringOrURI 는 JSON 문자열 값인데,
임의의 문자열을 써도 되지만(MAY) ":" 문자가 들어간 값은 URI 여야 합니다(MUST). 비교는
어떤 변환이나 정규화도 없이 대소문자를 구분하는 문자열로 합니다. NumericDate 는
1970-01-01T00:00:00Z UTC 부터 그 시각까지의 초를 나타내는 JSON 수치 값이고 윤초는 무시합니다.
하루를 정확히 86400초로 세는 POSIX(Portable Operating System Interface, 이식 가능 운영체제
인터페이스)의 "Seconds Since the Epoch" 정의와 같되, 정수가 아닌 값도 표현할 수 있다는 점이
다릅니다.
클레임 이름의 유일성과 모르는 클레임
§4 는 하나의 클레임 세트 안에서 클레임 이름이 유일해야 한다고 적습니다(MUST). JWT 파서는 클레임 이름이 중복된 JWT 를 거부하거나, 어휘적으로 마지막에 나온 중복 멤버 이름만 돌려주는 JSON 파서를 써야 합니다(MUST). 그리고 유효한 JWT 가 되려면 어떤 클레임이 들어 있어야 하는지는 문맥에 달렸고 이 명세의 범위 밖이라고 적습니다. 그런 요구가 따로 없다면, 구현이 이해하지 못하는 클레임은 전부 무시해야 합니다(MUST).
typ 과 cty
§5 는 JWT 가 JWS 일 때와 JWE 일 때 양쪽에서 쓰이는 헤더 파라미터 둘을 더 규정합니다.
| 헤더 파라미터 | 무엇을 적나 | 요구 강도 |
|---|---|---|
typ (§5.1) |
이 JWT 전체의 미디어 타입 | 사용은 OPTIONAL. 있다면 값이 "JWT" 이기를 RECOMMENDED. 미디어 타입 이름은 대소문자를 안 가리지만 레거시 구현과의 호환을 위해 언제나 대문자로 적기를 RECOMMENDED |
cty (§5.2) |
JWT 의 구조 정보 | 중첩 서명·암호화를 안 쓰는 보통의 경우에는 NOT RECOMMENDED. 중첩을 쓰는 경우에는 반드시 있어야 하고(MUST) 값이 "JWT" 여야 한다(MUST) |
typ 은 JWT 가 아닌 값도 함께 들어갈 수 있는 데이터 구조에서 종류를 가려내라고 둔
파라미터입니다. 대상이 JWT 임을 이미 아는 상황에서는 대개 쓰이지 않습니다. JWT 구현은 이
파라미터를 무시하고, 이 파라미터의 처리는 JWT 애플리케이션이 수행합니다.
받는 쪽이 해야 하는 검증
§7.2 가 검증 절차를 열 단계로 적습니다. 단계들의 입출력 사이에 의존이 없는 경우에는 단계의 순서가 중요하지 않습니다. 나열된 단계 가운데 하나라도 실패하면 그 JWT 를 거부해야 합니다(MUST). 애플리케이션이 잘못된 입력으로 취급한다는 뜻입니다.
flowchart TD
A["점이 하나라도 있나"] --> B["첫 점 앞부분을 base64url 디코드"]
B --> C["JOSE 헤더가 온전한 JSON 객체인가"]
C --> D["JWS 인가 JWE 인가 판정"]
D --> E["JWS 규칙 또는 JWE 규칙으로 검증"]
E --> F{"cty 가 JWT 인가"}
F -->|그렇다| A
F -->|아니다| G["메시지를 디코드해 클레임 세트를 얻는다"]
먼저 JWT 에 점 문자가 하나 이상 있는지 확인합니다. 첫 점 앞부분을 인코딩된 JOSE 헤더로
보고, 줄바꿈이나 공백 같은 문자가 섞이지 않았다는 제약 아래 base64url 디코드합니다. 그
결과가 RFC 7159 를 따르는 온전한 JSON 객체의 UTF-8 표현인지 확인하고, 그 객체를 JOSE
헤더로 삼습니다. 헤더에 문법과 의미를 모두 이해하고 지원하는 파라미터와 값만 들어 있는지,
아니면 이해 못 할 때 무시하도록 명시된 것인지 확인합니다. 그다음 이 JWT 가 JWS 인지 JWE
인지 판정하고, JWS 면 JWS 명세의 검증 단계를, JWE 면 JWE 명세의 검증 단계를 따릅니다.
JOSE 헤더의 cty 값이 "JWT" 이면 얻어낸 메시지 자체가 또 하나의 JWT 이므로 그 메시지를
JWT 로 삼아 1단계로 돌아갑니다. 그렇지 않으면 메시지를 base64url 디코드하고, 그 결과가
온전한 JSON 객체인지 확인해 클레임 세트로 삼습니다.
절차 끝에는 단서가 하나 붙습니다. 주어진 문맥에서 어떤 알고리즘을 써도 되는지는 애플리케이션의 결정입니다. JWT 검증에 성공하더라도 그 JWT 에 쓰인 알고리즘이 애플리케이션이 받아들일 만한 것이 아니라면, 애플리케이션은 그 JWT 를 거부하는 것이 좋습니다(SHOULD).
명세가 구현에 넘긴 자리
| 자리 | 명세가 적은 것 |
|---|---|
| 유효한 JWT 의 필수 클레임 | 문맥에 달렸고 이 명세의 범위 밖 |
| 시계 어긋남 여유 | 구현자가 둘 수 있다(MAY). 보통 몇 분 이내 |
| 허용 알고리즘 | 애플리케이션의 결정 |
typ 의 처리 |
JWT 구현이 아니라 JWT 애플리케이션이 한다 |
iss · sub · aud 의 해석 |
일반적으로 애플리케이션마다 다르다 |
이름을 관리하는 레지스트리
클레임 이름은 IANA 의 「JSON Web Token Claims」 레지스트리가 관리합니다. 등록 절차는
Specification Required 이고, 참조 문서로 RFC 7519 가 걸려 있습니다. 지정 전문가로는 Brian
Campbell · Mike Jones · Nat Sakimura · Filip Skokan 이 적혀 있습니다. 주소는
https://www.iana.org/assignments/jwt 입니다.
§9 는 레지스트리를 하나 더 씁니다. 미디어 타입이 아니라 URI 로 콘텐츠 타입을 선언하는
애플리케이션을 위해, 가리키는 내용이 JWT 임을 나타내는 URN(Uniform Resource Name, 통합 자원
이름) urn:ietf:params:oauth:token-type:jwt 를 등록합니다.
예시
HS256 으로 MAC 을 붙인 JWT
§3.1 의 예시입니다. 이 JOSE 헤더는 인코딩된 객체가 JWT 이고, 그 JWT 가 HMAC(Hash-based Message Authentication Code, 해시 기반 메시지 인증 코드) SHA-256 알고리즘으로 MAC 이 붙은 JWS 라고 선언합니다.
{"typ":"JWT",
"alg":"HS256"}
이 JOSE 헤더의 UTF-8 표현을 이루는 옥텟을 base64url 로 인코딩하면 이 값이 나옵니다.
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
클레임 세트는 이렇습니다. 이름 하나는 URI 모양입니다.
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
이 클레임 세트의 UTF-8 표현이 JWS 페이로드입니다. 그것을 base64url 로 인코딩하면 이 값이 나옵니다. 줄바꿈은 보기 편하라고 넣은 것입니다.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly
9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
인코딩된 JOSE 헤더와 인코딩된 JWS 페이로드의 MAC 을 HMAC SHA-256 으로 계산하고 그 값을 base64url 로 인코딩하면 이 JWS 서명이 나옵니다.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
이 세 조각을 이 순서대로 점 문자를 사이에 두고 이어 붙인 것이 완성된 JWT 입니다. 여기서도 줄바꿈은 보기 편하라고 넣은 것입니다.
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
서명이 없는 JWT
§6.1 의 예시입니다. JOSE 헤더가 이 객체를 Unsecured JWT 로 선언합니다.
{"alg":"none"}
인코딩하면 이렇습니다.
eyJhbGciOiJub25lIn0
클레임 세트는 §3.1 의 것과 같습니다.
{"iss":"joe",
"exp":1300819380,
"http://example.com/is_root":true}
인코딩된 JWS 페이로드도 같습니다.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
인코딩된 JWS 서명은 빈 문자열입니다. 그래서 점으로 이어 붙인 완성본은 점으로 끝납니다.
eyJhbGciOiJub25lIn0
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
갈래
클레임 세트에 무엇을 씌우느냐가 갈리는 축입니다. JOSE 헤더가 그 결과를 적기 때문에, 받는 쪽은 헤더를 읽고 어느 갈래인지 판정합니다.
JWS 로 표현된 JWT
JOSE 헤더가 JWS 의 것이면 이 JWT 는 JWS 로 표현된 것이고, 클레임에 디지털 서명이 붙거나 MAC 이 붙습니다. 이때 클레임 세트가 JWS 페이로드가 됩니다. 겉모양은 JWS 컴팩트 직렬화가 정합니다.
JWE 로 표현된 JWT
JOSE 헤더가 JWE 의 것이면 이 JWT 는 JWE 로 표현된 것이고, 클레임이 암호화됩니다. 이때 클레임 세트는 JWE 가 암호화하는 평문이 됩니다. 겉모양은 JWE 컴팩트 직렬화가 정합니다.
Unsecured JWT
클레임이 무결성 보호도 암호화도 안 된 JWT 입니다. §6 은 JWT 안에 든 서명이나 암호화가 아닌 다른 수단으로 내용이 보호되는 경우를 위해 이런 판을 허용합니다. JWT 를 담은 데이터 구조에 서명이 붙어 있는 경우 같은 것입니다. 이때 JWT 는 서명이나 암호화 없이 만들어질 수 있습니다(MAY).
모양으로는 alg 헤더 파라미터 값이 "none" 이고 JWS 서명 값이 빈 문자열인 JWS 입니다.
그 정의는 JWA(JSON Web Algorithms, JSON 웹 알고리즘) 명세에 있습니다. 클레임 세트를 JWS
페이로드로 갖는 Unsecured JWS 라고 §6 이 적습니다.
Nested JWT
중첩 서명이나 중첩 암호화를 쓴 JWT 입니다. JWT 하나가 바깥 JWS 의 페이로드나 바깥 JWE 의
평문 자리에 들어갑니다. 이 경우 cty 헤더 파라미터가 반드시 있어야 하고(MUST) 값이 "JWT"
여야 합니다(MUST). 검증하는 쪽은 그 값을 보고 1단계로 돌아가 안쪽 JWT 를 다시 검증합니다.
부록 A.2 가 실제 예를 보입니다. 클레임 세트를 먼저 서명하고, 그 서명된 JWT 를 다시
수신자에게 암호화합니다. 키 암호화에는 RSAES-PKCS1-v1_5 를, 평문의 인증 암호화에는
AES_128_CBC_HMAC_SHA_256 을 씁니다. 그 JOSE 헤더가
{"alg":"RSA1_5","enc":"A128CBC-HS256","cty":"JWT"} 입니다.
관련 항목
이것이 속하는 JOSE 표준군
JWS · JWE · JWA · JWK
이것을 이루는 구성 요소
JOSE 헤더 · JWS 컴팩트 직렬화 · JWE 컴팩트 직렬화 · 클레임 · 클레임 세트
이것을 정의·갱신하는 문서와 레지스트리
RFC 7519 · RFC 8725 · RFC 2119 · RFC 7515 · RFC 7516 · RFC 7518 · IANA JSON Web Token Claims 레지스트리 · ISSN · BCP · Standards Track
클레임 값과 이름에 쓰는 인코딩·형식
StringOrURI · NumericDate · Collision-Resistant Name · base64url · JSON · UTF-8 · URI · UUID · OID · 도메인 이름 · POSIX
예시가 쓰는 암호 알고리즘
MAC · HMAC · RSAES-PKCS1-v1_5 · AES_128_CBC_HMAC_SHA_256
이것과 조밀함으로 겨루는 대안 형식
SAML · SWT · XML 디지털 서명 · XML 정규화
이것이 실리는 위치
HTTP · HTTP Authorization 헤더 · URI 쿼리 파라미터
이것의 콘텐츠 타입을 가리키는 URN과 표준
URN · OAuth · RFC 6755
이것을 만들고 관리하는 조직
다른 이름: JSON Web Token