Tips/인증 인가

JWT (JSON Web Token)

dextto™ 2021. 11. 6. 19:43

JWT([jot]라고 읽는다고 하지만 국내에서는 그렇게 읽는 사람을 본적은 없습니다.😅)는 RFC 7519에 소개된 것으로써 문서의 초록에 따르면 다음과 같습니다.

JWT는 두 당사자 사이에 이전될 수 있는 클레임을 나타내는 간결하고 URL에 안전한 방법입니다. JWT에 포함된 클레임은 JSON 객체로 인코딩되어 JSON 웹 서명(JWS, JSON Web Signature)의 페이로드 또는 JSON 웹 암호화(JWE, JSON Web Encryption)의 일반 텍스트로 사용됩니다. 클레임을 디지털 방식으로 서명하거나 메시지 인증 코드(MAC, Message Authentication Code)로 암호화 되어 무결성을 보호합니다.

 

jwt.io 에서 JSON 객체로 JWT를 인코딩해 보거나 인코딩되어 있는 JWT를 입력하여 JSON 객체로 디코딩 해 보실 수 있습니다.

 

JWT.IO

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.

jwt.io

JWT의 구성요소

JWT는 헤더, 페이로드, 시그너처 3가지 요소를 가지며 점(.)으로 구분됩니다. 헤더와 페이로드는 각각 base64로 인코딩되어 있습니다. base64로 인코딩을 하면 사람이 읽을 수 없고 디코딩이 필요하지만 JWT를 HTTP 헤더나 요청 파라미터 또는 폼 파라미터로 사용하거나, JSON 문자열이 데이터베이스나 프로그래밍 언어에서 지원하지 않는 문자열을 사용할 경우는 사용이 불가능 하기 때문에 base64 인코딩을 합니다.

헤더

먼저 점(.)으로 구분된 가장 첫번째 문자열은 헤더입니다. 헤더는 일반적으로 JWT의 유형("typ")과 어떤 알고리즘("alg")에 의해 인코딩되었는 지를 포함합니다.

{
    "typ":"JWT",
    "alg":"HS256"
}

"typ" 파라미터는 JWSJWE에 정의된 미디어 타입입니다. 이는 JWT를 처리하는 애플리케이션에게 페이로드가 무엇인지를 알려주는 역할을 합니다. 즉, 이 토큰은 JWT라는 것을 뜻하므로 "JWT"라는 값으로 정의하라고 권고하고 있습니다.

"alg" 파라미터는 토큰을 암호화 하는 알고리즘입니다. 암호화하지 않을 경우는 "none"으로 정의하고, 암호화를 할 경우 해당 알고리즘을 기술합니다. 위의 예에서는 HS256으로 토큰을 암호화했다는 뜻입니다.

페이로드

페이로드에는 클레임(claim)이라 부르는 정보를 포함합니다.

표준 클레임

JWT 스펙에서는 다음과 같은 표준 클레임을 정의하고 있습니다.

 "iss"(Issuer, 토큰 발급자): . 누가 토큰을 발급(생성)했는지를 나타냅니다. 애플리케이션에서 임의로 정의한 문자열 또는 URI 형식을 가집니다.

"sub" (Subject, 토큰 주제): 일반적으로 주제에 대한 설명을 나타냅니다. 토큰 주제는 발급자가 정의하는 문맥상 또는 전역으로 유일한 값을 가져야 합니다. 문자열 또는 URI 형식을 가집니다.

"aud" (Audience, 토큰 수신자): 누구에게 토큰이 전달되는 가를 나타냅니다. 주로 보호된 리소스의 URL을 값으로 설정합니다.

"exp" (Expiration, 만료 시간): 언제 토큰이 만료되는지를 나타냅니다. 만료 시간이 지난 토큰은 수락되어서는 안됩니다. 일반적으로 UNIX Epoch 시간을 사용합니다.

"nbf" (Not Before): 정의된 시간 이후에 토큰이 활성됩니다. 토큰이 유효해 지는 시간 이전에 미리 발급되는 경우 사용합니다. 일반적으로 UNIX Epoch 시간을 사용합니다.

"iat" (Issued At, 토큰 발급 시간): 언제 토큰이 발급되었는지를 나타냅니다. 일반적으로 UNIX Epoch 시간을 사용합니다.

"jti" (JWT ID, 토큰 식별자): 토큰의 고유 식별자로써 같은 값을 가질 확률이 없는 암호학적 방법으로 생성되어야 합니다. 공격자가 JWT를 재사용하는 것을 방지하기 위해 사용합니다.

공개(Public) 클레임

JWT 발급자는 표준 클레임에 덧붙여 공개되어도 무방한 페이로드를 공개 클레임으로 정의합니다. 하지만 이름 충돌을 방지하기 위해 IANA "JSON Web Token Claims" 레지스트리에 클레임 이름을 등록하거나 합리적인 예방 조치를 해야 합니다. 보통 URI 형식으로 정의합니다.

{
    "http://example.com/is_root": true
}

비공개(Private) 클레임

JWT 발급자와 사용자간에 사용하기로 약속한 클레임입니다. 이름 충돌이 발생하지 않도록 주의해야 합니다.

시그너처

헤더와 페이로드는 단순히 base64로 인코딩하기 때문에 공격자는 토큰을 원하는 값을 넣고 생성할 수 있습니다. 따라서 생성된 토큰이 유효한 지 검증하는 장치가 필요합니다. 헤더에서 "alg":"HS256" 이라고 선언했으므로 이 토큰은 HMACSHA256 알고리즘으로 암호화해야 합니다. 당연히 암호화 할 때 사용하는 secret은 토큰을 생성하고 검증하는 서버에서만 안전한 방법으로 저장하고 있어야 합니다.

HS256 방식으로 암호화하는 것은 jwt.io의 예에서 나와 있듯이 헤더와 페이로드를 base64로 인코딩한 문자열과 secret(여기서는 'secret'이라는 문자열을 사용)을 이용하여 HMACSHA256 알고리즘에 넣어주면 됩니다.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
    'secret'
)

💡 JWT 토큰을 생성할 때는 직접 base64 인코딩과 암호화 알고리즘을 사용하지 않고 JWT 생성 라이브러리를 사용합니다. 이후 예시에서 사용해 보겠습니다.

앞의 JWT를 공격자가 다시 구성하여 페이로드를 수정한 후 secret을 WRONG_SECRET으로 잘못 사용한 JWT를 생성했다고 해 봅시다.

"http://example.com/is_root": false

이렇게 생성된 토큰(eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLCJleHAiOjEzMDA4MTkzODAsImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290IjpmYWxzZX0.Odfx2MvLVLzXYBaV8l1tlTY5Xifa46F8emgZudbhT04)을 다시 jwt.io 에서 우측 Decoded에 정상적인 암호(secret)를 제대로 넣고, 좌측 Encoded 영역에 붙여 넣으면 Invalid Signature라고 표시됩니다.

반응형

'Tips > 인증 인가' 카테고리의 다른 글

슬라이딩 세션과 리프레시 토큰  (0) 2021.11.17
인증(Authentication) vs. 인가(Authorization)  (0) 2021.11.14