JWT를 분석하고 사용해보자 (1/3)
해당 게시물은 시리즈이다.
목차
1편 : 개념 / 개요 / 프로토콜 설명
2편 : JWT(JWS) 보안 취약점 / 준수사항 분석
3편 : 실제로 구현해보자
JWT 개요 및 구조 설명
HTTP의 Stateless와 Stateful
먼저 위의 개념을 간단하게 이해하는 것이 JWT를 이해하는데 훨씬 수월하다. HTTP 프로토콜의 경우, Stateless 프로토콜이다. 즉, Request하고 Response하면 끝나는 것이다. 상태가 없다는 의미다. 그러나 웹 서비스의 경우, Stateless하게 운영하게 되면 문제점은 지속적인 Credential 교환과 상태(로그인 정보/장바구니 등의 비즈니스 정보) 유지를 위한 빈번한 I/O 발생이다. 이런 문제점 때문에 쿠키/세션을 적용하며, 웹서비스 중에 쿠키/세션을 쓰지 않는 웹서비스는 거의 없을 것이다.
정리하면, Stateless와 Stateful의 큰차이점은 싸이클의 차이이다.
앞으로 설명할 JWT는 Stateless한 서비스에서 식별/인증/인가를 하기 위한 모델이며, Stateless 입장에서 생각하는게 이해하기 쉽다.
JWT 무엇인가
먼저 토큰은 토큰 기반 웹서비스 또는 REST API 웹서버에서 많이 사용된다. 해당 토큰 사용방식에 대해 규격화/구조화(즉, 표준)한 것 중 하나를 JWT(JSON Web Token)라고 생각하면 되겠다.
왜/언제 사용하는지에 대한 예는 아래와 같다.
보통의 웹 서비스(stateful)는 쿠키/세션을 활용하여 로그인 서비스 즉, 사용자 식별/인증/인가가 이루어진다. 그러나 REST API 웹서버의 경우 쿠키/세션을 활용하지 않는다. 즉, Stateless한 서버이다. 그러면 리소스(URI)에 접근할 때 식별/인증/인가는 어떻게 진행되는가? 이 때 사용되는 것이 토큰이며 앞으로 설명할 JWT가 될 것이다(토큰은 목적이며 JWT 수단이다. 즉, 다른 기술을(JWT 말고) 적용할 수 있다.).
목적
JWT의 목적은 서버 리소스에 접근하는 주체에 대해 식별/인증/인가를 진행하는 것이다.
JWT 사이클
1. 사용자는 ID/PW를 서버에 전송한다.
2. 서버는 ID/PW 인증을 실시한다. 성공할 경우, JWT 토큰을 발급한다.
3. 사용자는 JWT 토큰을 사용하여(헤더에 적재) 리소스에 접근한다.
4. 서버는 사용자의 JWT 토큰 무결성/유효성 검증을 실시한 후, 리소스를 반환한다.
JWT(JWS) 구조
위의 JWT 라이프 사이클에서 반환한 JWT 토큰의 구조는 아래와 같다(JWT는 인터페이스이며 구현체는 JWS와 JWE가 있다. 보편적으로 JWT를 얘기하면 JWS를 칭하는것과 같다. 앞으로 JWT라고 언급하면 JWS를 언급한다고 생각하면 되겠다.).
크게 헤더/페이로드/시그니처 3개의 부분으로 나누어져 있다. 구성은 아래와 같다.
- 헤더: 토큰의 무결성 검증을 위한 값들을 지정
- 페이로드: 토큰의 유효성 검사 및 비즈니스 로직에서 활용할 때 사용하는 정보
- 시그니처: 토큰의 무결성 검증
JWT는 JSON으로 구성되어 있으며 구성요소는 아래와 같다. 위의 3가지 요소의 구분자는 “.” 온점이다. JSON으로 구성되어 있는 메시지를 Base64로 인코딩한다(정확히 얘기하면 Base64URL 인코딩을 한다. Base64와 Base64URL은 약간 다르다.).
전체적인 헤더 페이로드 구성은 토큰을 발급하는 서버가 항목을 지정한다. 예를 들어, 자주 사용되는 헤더는 아래와 같다.
- alg: Signature 생성 알고리즘
- typ: 토큰 타입
위의 2가지를 포함하여 더 많은 헤더를 적재할 수 있다. JOSE Header list의 링크는 아래와 같다.
- https://www.iana.org/assignments/jose/jose.xhtml
페이로드는 Claim 방식으로 구성되어 있다. Claim 방식의 토큰의 경우, 장점은 서비스를 호출한 사용자에 대한 추가 정보는 이미 토큰안에 들어가 있어서 추가적인 조회작업을 하지 않아도 된다.
클레임은 registered/public/private이 있다. Registered는 표준에 표시가된 표준 claim이며, public은 솔직히 모르겠다. Private claim은 사용자 정의 claim이다.
자주 사용되는 Registered claim은 아래와 같다.
- exp: 토큰 만료 날자
- sub: 토큰 발급 주체
- nbf: 토큰 사용 가능 날짜
- scope: 권한 관련
위의 3가지를 포함하여 더 많은 페이로드를 적재할 수 있다. JWT payload list의 링크는 아래와 같다.
- https://www.iana.org/assignments/jwt/jwt.xhtml
private claim의 경우, 아래와 같이 구성할 수 있다.
- name: 이름 값
- scope: 인가 값
시그니처를 생성하는 방법은 크게 2가지가 있다. 대칭키와 공개키 방식이다. 대칭키 방식의 경우, HMAC을 이용하여 Signature 값을 생성하고, 공개키 방식의 경우 서명을 통해 Signature 값을 생성한다.
JWT 발급 및 사용 순서
1. 사용자는 ID/PW를 서버에 전송한다.
2. 서버는 ID/PW 인증 성공 후, JWT 토큰을 아래와 같이 생성한다.
- 헤더 생성
- 페이로드 생성( 토큰 인증 후 사용할 정보 또는 토큰 인증 때 사용할 정보)
- 대칭키 또는 키쌍(개인키)을 통해 Signature 생성(헤더+페이로드 값으로)
3. 사용자에게 토큰 반환
4. 사용자는 해당 토큰을 사용할 때 HTTP Header에 Base64로 인코딩된 JWT(Header.Payload.Signature) 토큰을 적재하여 서버에 전송한다.
5. 서버는 토큰에 대해 무결성 검증을 실시한다. Signature 값 검증, 토큰 유효기간 검증 등의 작업을 실시한다.
6. 토큰의 무결성/유효성 검증 성공 후 사용자에게 리소스를 반환한다.