JWT (JSON Web Token) 이해하기
Writer: 서양훈

JWT 란?

JWT (JSON Web Token) 는 약자대로 JSON 객체를 이용해서 토큰 자체에 여러 정보를 담을수 있고 토큰을 이용해 인증 처리 할 수 있는 것을 말한다.

JWT 구조

Formula

JWT 구조는 3가지로 분류 할 수 있는데

header 는 signature 를 해싱 하기 위한 알고리즘 정보를 담는 공간이다.

payload

payload 는 서버와 클라이언트 주고 받을 수 있는 정보(시스템에 사용 되는 정보) 를 담을 수 있는 공간이다.

signature

signature 는 해당 Token 의 유효성 검증 하기 위한 정보 공간 이다. 이 signature 통해 해당 Token 이 유효한지 체크 하게 된다.

자세히 알아보자

실제로 JWT 토큰 예시를 보자


암호화된 데이터가 보인다. 여기서 JWT 특징은 자세히 보면 "." 으로 3개 데이터가 합쳐져 있다는것을 볼 수 있다. 하나하나씩 알아보자

#### header 정보 예시
```markdown
base64UrlEncode({
  "alg": "HS256"
})

결과값: eyJhbGciOiJIUzI1NiJ9

alg: 해싱 알고리즘을 지정한다. 해싱 알고리즘으로 보통 HMAC, SHA256, RSA 등 사용된다.

지정한 알고리즘은 해당 토큰이 유효한지 체크 할 때 사용되는 signature 부분에서 사용된다.

typ: 예시에서는 안보이는데 토큰의 타입을 지정한다. 우리는 JWT 이니 보통 ‘JWT’ 로 지정 한다.

마지막으로 base64 로 인코딩 하자.

payload 정보 예시

base64UrlEncode({
  "sub": "syh8088",
  "exp": 1633184408,
  "iat": 1632579608
})

결과값: eyJzdWIiOiJzeWg4MDg4IiwiZXhwIjoxNjMzMTg0NDA4LCJpYXQiOjE2MzI1Nzk2MDh9

header 와 마찬가지로 해당 정보를 가지고 마지막으로 base64 로 인코딩 하자.

Payload 부분에는 토큰에 담을 정보가 들어가 있다.

여기서 담을 정보 하나하나가 ‘claim’ 이라고 한다. 즉 key: value 하나하나가 각각 claim 이다.

payload 에서는 여러개의 claim 정보를 담을 수 있는데.

크게 3가지로 분류 된다.

  1. 등록된 (registered) claim - 토큰에 대한 정보를 담기위한 미리 정의된 클레임의 집합이다. 등록된 클레임의 사용은 선택적으로 사용 가능하지만 사용하는 것을 권장한다.
  2. 공개 (public) claim - 공개 클레임은 사용자 정의 클레임으로. 공개용 정보 전달을 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용해야 한다.
  3. 비공개 (private) claim - 비공개 클레임은 등록된 클레임도 아니고, 공개 클레임도 아닌 당사자간에 정보를 공유하기 위해 만들어진 사용자지정 클레임이다. 공개 클레임과 달리 이름이 중복되어 충돌이 될 수 있으니 유의 해야 한다.

예시에서는 등록된 (registered) claim 만 확인 할 수 있다.

iss : 토큰 발급자 (issure)

sub : 토큰 제목 (subject)

aud : 토큰 대상자 (audience)

exp : 토큰 만료시간 (expiration), 시간은 NumericDate ( ex :1480849147370 ) 형식이어야 하며 언제나 현재 시간 이후여야 한다.

nbf : Not Before 를 의미 하며, 토큰 활성 날짜와 비슷한 개념이다. NumericDate 형식이어야 하며, 이 날짜기 지나기 전까지는 토큰을 처리하지 않는다.

iat : 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age가 얼마나 되었는지 판단 할 수 있다.

jti : JWT 의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용 된다. 일회용 토큰에 사용하면 유용하다.

signature 정보 예시

base64UrlEncode(HMACSHA256(${header 결과값}.${payload 결과값}, secret))

결과값: IM_ZOrigf_UhpPxH8O4cT_9B8bz4EobFGN_LFicKuG8

마지막으로 signature 는 위에 각각 header, payload 결과값을 . 으로 합친 다음에

header 에 정의된 알고리즘과 비밀키를 이용해서 암호화 한 다음에 base64 로 인코딩 한 값이 signature 값이 되겠다.

※ 이때 비밀키는 절대 외부에 노출 해서는 안된다. 즉 서버측에서만 알아야 하는 정보 이다. 참고로 비밀키 또는 비대칭키 방식으로 할 수 있다.

이렇게 각각 header, payload, signature 값을 . 으로 합치면

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzeWg4MDg4IiwiZXhwIjoxNjMzMTg0NDA4LCJpYXQiOjE2MzI1Nzk2MDh9.IO9BVfW17nqHNgy5RmxCbGsOM6s485cH812eoSYvsMM

이렇게 JWT Token 이 완성 된다.

JWT 검증 방법

이제부터 JWT 가 어떻게 해당 토큰이 유효한 것인지 알아보겠다.

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJzeWg4MDg4IiwiZXhwIjoxNjMzMTg0NDA4LCJpYXQiOjE2MzI1Nzk2MDh9.IO9BVfW17nqHNgy5RmxCbGsOM6s485cH812eoSYvsMM

여기 우리가 만든 JWT 토큰이 있다. 이 토큰이 클라이언트로부터 서버측에 값을 전달 받았다고 가정 해보자

서버측은 이 토큰을 . 으로 연결된 header, payload 를 추출 한다.

base64UrlEncode(HMACSHA256(${header 결과값}.${payload 결과값}, secret))

추출한 header, payload 값을 . 으로 연결 하고 동시에 header 에 정의된 알고리즘 및 외부에 절대 노출 해서는 안되는 비밀키를 이용해 암호화 하게 되고 base64로 인코딩 한다.

Formula

이렇게 서버측으로 부터 만들어진 값과 기존에 클라이언트으로 부터 받은 signature 값을 비교 하게 되고 만약 틀리면 검증에 실패하게 되고 맞다면 검증에 성공 하게 된다.

세션 및 쿠키와 JWT 서버 인증 방식 차이점

JWT 를 활용해 서버 인증 방식을 자세히 알아보고 기존에 세션 방식과 무슨 차이점이 있는지 알아보겠다.

참고로 각각의 방식에 장점과 단점이 모두 존재한다.

세션 및 쿠키 이용한 인증 방식

Formula

이미지를 보면 클라이언트 으로부터 ID, PASSWORD 를 받아 서버측은 인증 처리하게 되고 인증 처리를 완료 된다면 세션저장소에 세션 생성 및 생성한 세션을

클라이언트에 전달하게 됩니다.

전닫 받은 세션은 웹브라우저의 쿠키 및 로컬 스토리지에 저장 하게 됩니다. 보통은 보안을 위해 세션을 저장합니다.

로컬 스토리이지에 저장 하게 된다면 javascript 코드로 접근이 가능하기 때문에 XSS 공격에 취약하다는 단점이 있기 때문 입니다.

반면 쿠키에 저장 하게 된다면 httpOnly, secure 옵셥 통해 보안이 가능 합니다.

이건 공통적으로 보안 이슈이지만 CSRF 공격에 취약 하다고 볼 수 있다.

해결 방안은 여러가지 이지만 보통은 로그인 및 인증이 필요한 페이지를 접속 할때마다

새로운 세션을 발급 받는 방식과 동시에 기존 세션을 삭제 하는 방식 이고

또 CSRF 공격자는 HTTP header 을 이용해서 요청 할 수 없다는 것을 이용해 이를 활용 하는 방법도 있다.

토큰 이용한 인증 방식

Formula

처음에는 초기 로그인은 세션 및 쿠키를 이용한 방식과 같고 서버로 부터 생성한 access token 은 클라이언트에 전달 하게 됩니다.

클라이언트는 인증 페이지에 접속시 발급받은 토큰을 이용해서 서버측에 전달하고 서버는 유효한 토큰인지 체크 합니다.

여기서 기존 세션 및 쿠키 방식과 다른점은 어딘가 세션저장소가 필요 없다는 점이다. (일반적으로 그렇지 대규모 서버 사이드 구축시에는 여러 방면으로 필요할수 있습니다.)

한마리로 Stateless 한 서버를 구축 할 수 있다.

기존 세션 및 쿠키 방식의 세션 저장소 경우 Redis, DB 을 활용한다는 점인데 토큰은 Stateless 하게 추가 저장소가 필요 없다.

하지만 단점도 분명 존재한다.

access token 노출 하게 된다면 해당 상태값을 돌이킬수 없게 된다.

기존 세션 및 쿠키 방식은 세션 저장소에 해당 세션을 삭제 하면 불가능한 인증 상태로 변경 할 수 있는데 일반적으로 토큰 방식은 불가능 하다.

보통 이를 방지 하기 위해 access token 유효 길이를 짧게 하고 refresh token 을 이용해서 새로운 토큰 발급을 할 수 있다.