JWT(JSON Web Token)

cabbage·2023년 1월 30일
0

기타

목록 보기
1/26

JWT 이해하기

JWT(JSON Web Token)은 인증 유저를 식별하기 위해 가장 일반적으로 사용된다. JWT는 일반적으로 인증 서버에서 발급되고 클라이언트 서버에서 API의 보안을 위해 가장 많이 사용한다.

JWT란?

JWT은 주로 클라이언트와 서버 사이에서 정보를 공유하기 위해 사용하는 개방형 산업 표준(open industry standard)이다.

클라이언트와 서버가 공유할 정보를 JSON에 넣는다. JWT는 이 JSON 객체를 포함한다. 각각의 JWT는 JWT 클레임이라고도 알려진 JSON 컨텐츠가 클라이언트에 의해 변경될 수 없도록 보장하기 위해 암호화(hashing)를 사용하여 서명된다.

예를 들어 구글에 로그인하는 경우, 구글은 다음과 같은 클레임/JSON 페이로드를 포함하는 JWT를 발행한다.

{
    "iss": "https://accounts.google.com",
    "azp": "1234987819200.apps.googleusercontent.com",
    "aud": "1234987819200.apps.googleusercontent.com",
    "sub": "10769150350006150715113082367",
    "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
    "email": "jsmith@example.com",
    "email_verified": "true",
    "iat": 1353601026,
    "exp": 1353604926,
    "nonce": "0394852-3190485-2490358",
    "hd": "example.com"
}

구글 로그인 기능을 사용하는 클라이언트 애플리케이션은 엔드유저가 누구인지를 발행된 JWT를 통해 알 수 있게 된다.

토큰은 무엇이고 왜 필요할까?

인증 서버가 평범한 JSON 객체로 정보를 전송할 수 없는 이유와 “토큰”이라는 것으로 변환해야 하는 이유는 무엇일까?

인증 서버가 정보를 평범한 JSON 객체로 전송한다면 클라이언트 애플리케이션의 API는 수신하는 컨텐츠가 정확한 것인지를 증명할 길이 없다. 예를 들어, 공격자가 유저 ID를 변경하면 애플리케이션의 API는 무슨 일이 벌어졌는지를 알 수가 없다.

이러한 보안 이슈 때문에 인증 서버는 클라이언트 애플리케이션이 유효성을 입증할 수 있는 방법으로 정보를 전송해야 한다. 여기에서 “토큰”이라는 개념이 등장한다.

토큰은 유효성이 안전하게 입증될 수 있는 정보가 포함된 문자열이다.

JWT의 구조

JWT는 세 가지 부분으로 구성된다.

  • Header(헤더)
    • 두 가지 부분으로 구성
      • 서명 알고리즘(signing algorithm)
      • 토큰 타입
  • Payload(페이로드)
    • 페이로드는 클레임 또는 JSON 객체를 포함한다.
  • Signature(서명, 시그니처)
    • JSON 페이로드의 무결성을 입증하기 위해 사용하는 암호화 알고리즘으로 생성된 문자열

https://supertokens.com/static/b0172cabbcd583dd4ed222bdb83fc51a/78612/jwt-structure.png

JWT 클레임 컨벤션

{
    "iss": "https://accounts.google.com",
    "azp": "1234987819200.apps.googleusercontent.com",
    "aud": "1234987819200.apps.googleusercontent.com",
    "sub": "10769150350006150715113082367",
    "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
    "email": "jsmith@example.com",
    "email_verified": "true",
    "iat": 1353601026,
    "exp": 1353604926,
    "nonce": "0394852-3190485-2490358",
    "hd": "example.com"
}

JSON 페이로드의 필드 이름에 대해 알아 보자.

  • iss : 토큰 발급자 (이 경우에는 구글)
  • azpaud : 애플리케이션을 위해 구글에서 발급한 클라이언트 ID이다. 구글은 이 방식으로 웹사이트가 자신들의 로그인 서비스를 사용하려고 하는 것을 알 수 있고, 웹사이트는 JWT가 특별히 해당 웹사이트를 위해 발급되었다는 것을 알 수 있다.
  • sub : 엔드유저의 구글 유저 ID
  • at_hash : 액세스 토큰의 해시이다. OAuth 액세스 토큰은 불분명한 토큰이라는 점에서 JWT와 다르다. 액세스 토큰의 목적은 클라이언트 애플리케이션이 로그인한 유저에 대해 더 많은 정보를 구글에게 요구하기 위한 것이다.
  • email : 엔드유저의 이메일 ID
  • email_verified : 유저가 인증된 이메일을 가지고 있는지 여부
  • iat : JWT가 생성된 시간
  • exp : JWT가 만료되는 시간
  • nonce : 리플레이 공격(replay attack)을 예방하기 위해 클라이언트 애플리케이션에서 사용된다.
  • hd : 유저의 호스팅된 G Suite 도메인

이런 특별한 키를 사용하는 이유는 JWT의 중요한 필드 이름이 산업 표준을 따르기 때문이다. 이 컨벤션을 따르면 다른 언어로된 클라이언트 라이브러리에서도 인증 서버에서 발행한 JWT의 유효성을 확인할 수 있다. 예를 들어, 클라이언트 라이브러리가 JWT가 만료되었는지 아닌지를 확인해야 한다면 exp 필드를 확인하면 된다.

JWT 동작 방식

1. JSON 생성하기

간단한 JSON 페이로드를 생성한다.

{
    "userId": "abcd123",
    "expiry": 1646635611301
}

2. JWT 서명 키(signing key)를 생성하고 서명 알고리즘을 결정하기

JWT의 서명(시그니처)를 생성하기 위한 서명 키(또는 시크릿 키)와 서명 알고리즘이 필요하다.

  • 서명 키: NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=
  • 서명 알고리즘: HMAC + SHA256 (HS256 이라고도 함)

3. “헤더” 생성하기

헤더는 토큰의 타입, 서명 알고리즘의 정보를 담고 있다.

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

4. 서명(signature) 생성하기

  • JSON 페이로드의 모든 공백을 제거 후 base64로 인코딩한다.
  • 이와 유사하게 JSON 헤더의 모든 공백을 제거 후 base64로 인코딩한다.
  • base64 인코딩 헤더와 페이로드를 점(.)을 사용해서 연결한다. (<header>.<payload>)
    이렇게 연결하는 이유는 산업 표준을 따르기 때문이다.
  • 위에서 연결한 문자열과 서명 키(또는 시크릿 키)에 대해 Base64 + HMACSHA256 함수를 실행한다.
Base64URLSafe(
    HMACSHA256("base64로 인코딩한 헤더" + "." + "base64로 인코딩한 페이로드", "서명키")
)

결과적으로 JWT의 마지막 부분인 서명(시그니처) 부분이 생성된다.
base64로 인코딩하는 이유도 산업 표준을 따르기 때문이다.

5. JWT 생성하기

마지막으로 생성한 서명을 뒤에 붙인다.

  • JWT = <header>.<payload>.<signature>
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ.3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M

6. JWT 유효성 검증하기

인증 서버는 자신이 발급한 JWT를 클라이언트에 응답한다.
클라이언트는 서버에 요청을 보낼 때 JWT를 첨부한다.
서버는 클라이언트가 보낸 JWT의 유효성을 검증하기 위해 아래와 같은 단계를 거친다.

먼저 JWT의 헤더를 검증한다.

  • JWT의 헤더를 Base64 디코딩한다.
    • typ 필드의 값이 JWT 인지, alg 필드의 값이 HS256 인지를 검증한다.
    • 만약 검증되지 않으면 JWT를 거절한다.

다음으로 JWT의 서명을 검증한다.

  • 서명 생성 과정을 한 번 더 진행한다.
  • 생성된 서명이 전달 받은 JWT의 서명과 같은지를 확인한다. 만약 같지 않다면 JWT를 거절한다.
    • 만약 전달 받은 JWT의 페이로드가 JWT를 생성할 때의 페이로드와 다르다면 이 단계에서 생성한 서명과 전달 받은 JWT의 서명이 달라진다.

마지막으로 JWT의 페이로드를 검증한다.

  • JWT 페이로드를 base64 디코딩한다.
  • 현재 시간이 디코딩한 페이로드의 exp 클레임의 값보다 크다면 JWT가 만료된 것이므로 JWT를 거절한다.

결과적으로 위에 있는 모든 검증 단계를 통과한 JWT만 신뢰할 수 있다.

JWT의 장점과 단점

장점

  • 보안
    • JWT는 시크릿 키(HMAC) 또는 퍼블릭/프라이빗 키 쌍(RSA 또는 ECDSA)을 사용하여 디지털 서명된다. 따라서 클라이언트나 공격자가 수정하지 못하도록 보호한다.
    • 만약 클라이언트나 공격자가 헤더나 페이로드를 수정하면 생성 당시의 서명과 전달 받은 서명이 달라진다.
  • 클라이언트에만 저장됨
    • 서버에서 JWT를 생성하고 클라이언트에게 전달한다. 그러면 클라이언트는 모든 요청마다 JWT를 제출한다. 이 점은 데이터베이스 공간을 절약하게 해준다.
  • 효율성 / Stateless
    • 데이터베이스 조회가 필요 없기 때문에 JWT의 유효성 검증은 빠르다.
    • 특히 대규모 분산 시스템에서 유용하다.

단점

  • 취소 불가
    • JWT의 독립적인 특성과 상태 비저장(stateless) 검증 프로세스로 인해 JWT가 만료되기 전에 JWT를 만료시키는 것이 어렵다.
  • 시크릿 키 의존
    • JWT 생성은 하나의 시크릿 키에 의존한다.
    • 만약 시크릿 키가 발각된 경우, 공격자는 API가 JWT를 허용하도록 JWT를 조작할 수 있다. 즉 시크릿 키를 알아낸다면 모든 유저의 신원을 속일 수 있다는 것을 의미한다.

참고

profile
캐비지 개발 블로그입니다. :)

0개의 댓글