치춘짱베리굿나이스

JWT 본문

이론적인 부분들/웹

JWT

치춘 2022. 10. 10. 18:15

JWT

JWT란?

https://jwt.io/introduction

JSON Web Token

JSON 형식의 데이터가 저장되어 있는 토큰으로, 보통 JSON 형태 그대로 사용하지 않고 base64로 인코딩하거나 암호화하여 해시 문자열과 비슷한 형태를 띈다

JSON 객체를 주고받는 하나의 방법이라 할 수 있으며, 토큰 기반 인증은 대부분 JWT로 구현한다

구성 요소

JWT는 헤더, 페이로드, 서명의 세 부분으로 나뉘어져 있다

헤더

{
    "alg": "HS256", // HS256 알고리즘을 사용하였다
    "typ": "JWT" // JWT 토큰 이라는 뜻

이 토큰의 종류와 암호화에 사용된 알고리즘 정보를 담고 있다

JWT 그 자체를 암호화한 것이 아닌, 후술할 서명 부분을 암호화하는 데에 사용된 알고리즘이다

대개 HMAC SHA256이나 RSA 등을 쓴다고 한다

헤더 부분은 base64로 인코딩된다

페이로드

{
    "iss": "chichoon", // 토큰을 발급한 주체
    "sub": "jwt-test", // 토큰 제목
    "iat": 1232415222, // 토큰 발급 시각 (Numeric Date)
    "exp": 1232415555, // 토큰 만료 시각 (Numeric Date)
    "id": "chichoon",
    "email": "chichoon.choi@gmail.com"
}

토큰의 본문 영역이다

본문의 각 키-값 쌍은 클레임 (Claim) 이라 부르며, JWT에는 세 종류의 클레임이 사용된다

  • Reserved Claim (등록된 클레임)
    • RFC7519 문서의 JWT 스펙에 공식적으로 이미 정의되어 있는 클레임들이다
    • JWT 공식 문서에는 ‘필수는 아니지만 권장하는’ (not mandatory but recommended) 사전 정의된 클레임 세트라고 설명하고 있다
    • iss: Issuer, JWT를 발급한 주체
    • sub: Subject, 이 토큰의 주제 (제목)
    • aud: Audience, JWT가 의도하는 수신자
    • exp: Expiration Date, 토큰 만료 시각
    • nbf: Not Before, JWT 토큰이 활성화되는 시각 (이 시각 이전까진 활성화 되지 말라는 뜻)
    • iat: Issued At, JWT가 발급된 시각
    • jti: JWT ID, JWT의 식별 ID, rfc에서는 동일한 JWT ID가 다른 데이터를 포함하고 있는 토큰에 중복 할당되지 않도록 심혈을 기울여 배정하라고 나와 있다
  • Public Claim (공개 클레임)
    • 밖에 공개되어도 상관없는 정보들이 들어간다
    • 클레임 이름이 충돌되면 안 되며, 이를 방지하기 위해 IANA JSON Web Token Registry에 사전 정의된 키를 사용하거나, URI를 클레임명으로 사용해야 한다
  • Private Claim (비공개 클레임)
    • 정보를 주고받는 주체 사이에서 협의 하에 사용하는 클레임으로, 이름은 무엇을 사용하든 상관없으나 공개 클레임, 등록된 클레임과 충돌이 발생하면 안된다

페이로드 부분 또한 Base64로 인코딩한다

서명

Signature

서명 부분을 구성하기 위해선 헤더, 페이로드가 전부 작성되어 있어야 하고, 비밀 키 (secret) 가 지정되어 있어야 한다

앞서 헤더에서 지정한 알고리즘을 사용하여 암호화할 것이다

 

HMACSHA256(
    base64UrlEncode(header) + '.' +
    base64UrlEncode(payload),
    secret) // 헤더와 페이로드를 . 기준으로 이어붙인 후, 비밀 키와 함께 인코딩

https://jwt.io/introduction 에 소개되어 있는 예제로, 헤더와 페이로드를 secret 키와 함께 HMAC SHA256 방식으로 인코딩한다

또한 이렇게 인코딩한 해시를 base64로 한번 더 인코딩해서 비로소 우리가 사용하는 JWT의 서명 부분을 만드는 것이다

 

sha256은 복호화가 안 되는데 서명 검증을 어떻게 하냐? 하면

  • 토큰을 수신받은 주체는 자신이 알고 있는 secret 키와, 서명 앞에 붙어있는 헤더 + 페이로드 문자열을 똑 떼어서 같은 방법으로 인코딩해 본다
  • SHA256과 같은 알고리즘은 문자열의 구성이 한 글자라도 다르면 전혀 다른 값을 반환한다
  • 따라서 만약 인코딩할 때 사용된 secret 키가 내가 가지고 있는 것과 다르거나, 헤더 또는 페이로드가 조작되었을 경우 수신받은 토큰의 서명 부분과 내가 직접 인코딩한 서명이 다르게 나올 것
  • 두 서명이 동일하게 나온다면, 유효한 JWT로 결론짓는다

다른 복잡한 외부 로직이나 키 등이 없이도 JWT 자체로 유효성 검사를 하기 매우 쉽다

헤더와 페이로드는 복호화가 가능한 base64로 인코딩되므로 마음만 먹으면 탈취해서 조작할 수 있는 반면 서명은 단방향 암호화를 한번 거친 후 base64로 인코딩하므로 조작이 불가능하다

또한, secret 키 덕분에 내가 원하는 발신자가 보낸 토큰이 맞는지 검증하기 쉽다는 장점도 있다

세 요소를 전부 이어붙이면

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
  • eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 : 헤더
  • eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ : 페이로드
  • SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c : 서명

헤더 - 페이로드 - 서명은 .을 기준으로 이어져 있다

특징

  • 데이터를 토큰에 담아 전송하기 때문에, 검증이나 데이터 사용을 위해 추가적으로 데이터베이스에 접근할 필요가 없으며, 서버와 독립적으로 운용된다
    • 이는 장점이자 단점인데, 데이터베이스 접근이 필요 없는 것은 분명한 장점이지만 토큰이 탈취된다면 서버가 알 방도가 없으며, 조치를 취할 수도 없기에 토큰이 만료되길 기다리는 수밖에 없다
    • 이 단점을 보완하기 위해 로그인과 같이 중요한 기능에는 Access Token과 Refresh Token과 같이 토큰을 두 개 이상 같이 사용하여 보안성을 높이는 방식을 채택한다
  • base64로 인코딩한다는 부분에서 알 수 있듯, 헤더와 페이로드는 언제든지 복호화가 가능하다
    • 만약 페이로드에 사용자의 계정 비밀번호와 같이 중요한 정보를 담아둔다면, 토큰이 탈취당했을 때 개인정보 유출이 발생할 수 있으므로 조심해야 한다
    • 중요하고 민감한 데이터는 절대 페이로드에 담지 말라고 jwt.io 에서도 언급하고 있다
    • 보안에 취약하다는 점을 보완하기 위해 서명이 추가되어 있으며, 서명은 단방향 암호화 (SHA256 등) 되기 때문에 적어도 페이로드나 헤더가 조작되었는지, 잘못된 시크릿 키로 인코딩되진 않았는지 여부를 체크하는 것으로 최소한의 안전장치를 마련할 수 있다

다른 토큰에 비교하면

  • Simple Web Tokens (SWT)
    • SWT는 ASP.NET web API에 최적화되어 있다고 하며, 대칭 키 방식으로 암호화한다고 한다
    • JWT는 x.509 인증서 형식의 공개키 / 개인키 쌍으로 암호화할 수 있다고 한다
    • JWT 쪽 방식이 조금 더 안정적이라고 한다

 

https://jwt.io/introduction

  • Security Assertion Markup Language Tokens (SAML)
    • XML 기반이라 인코딩 후에도 크기가 크다
    • JWT는 JSON 기반이며, JSON이 대체로 XML보다 가볍기 때문에 인코딩한 뒤에도 길이나 용량 면에서 JWT가 경량화되어 있다
    • JWT는 JSON을 주고받는 Restful API에 최적화되어 있고, SAML은 XML을 주고받는 SOAP (Simple Object Access Protocol) API 방식에 최적화되어 있다고 한다
    • 위의 이미지 (jwt.io에서 발췌) 만 봐도 용량 면에선 JWT의 압승인 것 같다 ㅋㅋㅋㅋㅋ

그 외에도 JSON 기반에 JSON Parser가 어지간한 언어에 구현이 되어 있으므로, 인터넷을 사용하는 모든 기기 (모바일 기기 포함) 에서 다루기가 용이하다는 장점이 크다

용례

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

제일 많이 사용되는 곳이 Access Token & Refresh Token을 통한 인증 & 인가 과정일 것이다

Access Token과 Refresh Token은 그 생명주기나 흐름이 복잡해서 다른 포스팅으로 분리하려고 한다

클라이언트에는 위와 같이 Bearer <토큰내용> 형식으로 쿠키에 저장되며, 서버에서는 헤더에 담아져 보내지는 쿠키를 읽고 검증하여 이 유저가 누구인지 인증한다

그 외에도 정보 교환에 종종 사용되곤 한다


참고자료

https://www.rfc-editor.org/rfc/rfc7519#section-4.1

https://cheony-y.tistory.com/270

https://lbadri.wordpress.com/2012/07/30/anatomy-of-a-simple-web-token-swt/

https://hudi.blog/self-made-jwt/

https://ssup2.github.io/theory_analysis/JWT/

'이론적인 부분들 > ' 카테고리의 다른 글

CORS  (0) 2023.06.17
DOM과 웹 렌더링  (0) 2023.05.19
로그인, 인증, 인가  (0) 2022.10.08
쿠키와 세션  (0) 2022.10.08
HTTP 기본  (0) 2022.08.04
Comments