JWT(JSON Web Token)은 인증 유저를 식별하기 위해 가장 일반적으로 사용된다. JWT는 일반적으로 인증 서버에서 발급되고 클라이언트 서버에서 API의 보안을 위해 가장 많이 사용한다.
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는 세 가지 부분으로 구성된다.
Header(헤더)
Payload(페이로드)
Signature(서명, 시그니처)
{
"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
: 토큰 발급자 (이 경우에는 구글)azp
와 aud
: 애플리케이션을 위해 구글에서 발급한 클라이언트 ID이다. 구글은 이 방식으로 웹사이트가 자신들의 로그인 서비스를 사용하려고 하는 것을 알 수 있고, 웹사이트는 JWT가 특별히 해당 웹사이트를 위해 발급되었다는 것을 알 수 있다.sub
: 엔드유저의 구글 유저 IDat_hash
: 액세스 토큰의 해시이다. OAuth 액세스 토큰은 불분명한 토큰이라는 점에서 JWT와 다르다. 액세스 토큰의 목적은 클라이언트 애플리케이션이 로그인한 유저에 대해 더 많은 정보를 구글에게 요구하기 위한 것이다.email
: 엔드유저의 이메일 IDemail_verified
: 유저가 인증된 이메일을 가지고 있는지 여부iat
: JWT가 생성된 시간exp
: JWT가 만료되는 시간nonce
: 리플레이 공격(replay attack)을 예방하기 위해 클라이언트 애플리케이션에서 사용된다.hd
: 유저의 호스팅된 G Suite 도메인이런 특별한 키를 사용하는 이유는 JWT의 중요한 필드 이름이 산업 표준을 따르기 때문이다. 이 컨벤션을 따르면 다른 언어로된 클라이언트 라이브러리에서도 인증 서버에서 발행한 JWT의 유효성을 확인할 수 있다. 예를 들어, 클라이언트 라이브러리가 JWT가 만료되었는지 아닌지를 확인해야 한다면 exp
필드를 확인하면 된다.
간단한 JSON 페이로드를 생성한다.
{
"userId": "abcd123",
"expiry": 1646635611301
}
JWT의 서명(시그니처)를 생성하기 위한 서명 키(또는 시크릿 키)와 서명 알고리즘이 필요하다.
NTNv7j0TuYARvmNMmWXo6fKvM4o6nv/aUi9ryX38ZH+L1bkrnD1ObOQ8JAUmHCBq7Iy7otZcyAagBLHVKvvYaIpmMuxmARQ97jUVG16Jkpkp1wXOPsrF9zwew6TpczyHkHgX5EuLg2MeBuiT/qJACs1J0apruOOJCg/gOtkjB4c=
HMAC + SHA256
(HS256
이라고도 함)헤더는 토큰의 타입, 서명 알고리즘의 정보를 담고 있다.
{
"typ": "JWT",
"alg": "HS256"
}
<header>.<payload>
)Base64URLSafe(
HMACSHA256("base64로 인코딩한 헤더" + "." + "base64로 인코딩한 페이로드", "서명키")
)
결과적으로 JWT의 마지막 부분인 서명(시그니처) 부분이 생성된다.
base64로 인코딩하는 이유도 산업 표준을 따르기 때문이다.
마지막으로 생성한 서명을 뒤에 붙인다.
<header>.<payload>.<signature>
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiJhYmNkMTIzIiwiZXhwaXJ5IjoxNjQ2NjM1NjExMzAxfQ.3Thp81rDFrKXr3WrY1MyMnNK8kKoZBX9lg-JwFznR-M
인증 서버는 자신이 발급한 JWT를 클라이언트에 응답한다.
클라이언트는 서버에 요청을 보낼 때 JWT를 첨부한다.
서버는 클라이언트가 보낸 JWT의 유효성을 검증하기 위해 아래와 같은 단계를 거친다.
먼저 JWT의 헤더를 검증한다.
typ
필드의 값이 JWT
인지, alg
필드의 값이 HS256
인지를 검증한다.다음으로 JWT의 서명을 검증한다.
마지막으로 JWT의 페이로드를 검증한다.
exp
클레임의 값보다 크다면 JWT가 만료된 것이므로 JWT를 거절한다.결과적으로 위에 있는 모든 검증 단계를 통과한 JWT만 신뢰할 수 있다.