-
JWT란? JWT의 구조와 인증 과정카테고리 없음 2022. 8. 28. 18:54
1. JWT란?
클라이언트가 서버의 특정 리소스를 사용하기 위해서는 사용자 인증을 해야합니다.
사용자 인증방식에는 대표적으로 쿠키, 세션, 토큰방식이 있고 오늘 설명드릴 JWT(JSON Web Token)는
토큰 인증 방식에서 제일 많이 알려진 인증 방식 중 하나입니다.
JWT는 이름 그대로 JSON으로 형태로 된 토큰이며 {header}.{payload}.{signature} 의 구조로 구성되어있습니다.
지금부터 JWT 구조를 하나하나 살펴보겠습니다.
[JWT의 예]
Authorization: Bearer eyJraWQiOjEsImFsZyI6IkhTMjU2IiwidHlwIjoiSldUIn0.eyJzdWIiOiJ1c2VybmFtZSIsImlhdCI6MTUxNjIzOTAyMiwiZXhwIjoxNTI2MjM5MDIyfQ.WYELdsQTjKfK3zxvG7UqrZMHGh_wI7DTi4T91BhcQqc
[위 JWT를 Base64로 디코딩한 예]
{ // Header
"kid": 1,
"alg": "HS256",
"typ": "JWT"
}.
{ // Payload
"sub": "username",
"iat": 1516239022,
"exp": 1526239022
}.Db4expMNQska0tfY5islCNQP0zDoNpjdzKT2QiSOr5g //Signature2. JWT 구조 - Header, Payload, Signature
2-1. Header
Header는 JWT를 검증하는데 필요한 데이터입니다.
이 정보를 JSON으로 변환해서 UTF-8로 인코딩한 뒤 BASE64 URL-Safe로 인코딩한 값이 들어있습니다.
{
"kid": 1,
"alg": "HS256",
"typ": "JWT"
}.- kid : Secret Key의 ID, JWT 암호화에 필요한 Secret Key(비밀키)를 식별해주는 정보입니다.
- alg : Algorithm을 줄인 말로 토큰을 암호화하는데 사용된 해시 알고리즘의 종류를 의미합니다.
- typ : Type의 줄인 말로 토큰의 타입을 의미합니다.
2-2. Payload
Payload는 실질적으로 사용자 인증에 필요한 데이터입니다.
Payload의 각각 필드들을 Claim이라고 부릅니다.
원하는 Claim은 얼마든지 추가할 수 있으나 민감정보는 포함하면 안됩니다.
{
"sub": "app@email.com",
"iss" : "app",
"iat": 1516239022,
"exp": 1526239022
}- sub : Subject를 줄인 말로 토큰의 주인을 의미합니다. 제 프로젝트 같은 경우에는 이메일이 유일한 값이기 때문에 이메일로 토큰의 주인을 판별했습니다. sub는 테이블의 기본키처럼 유일한 식별자여야 합니다.
- iss : Issuer를 줄인 말로 토큰을 발행한 주체입니다. 예를 들어 페이스북이 발행했으면 Facebook, 카카오면 Kakao가 되겠죠
- iat : issued at를 줄인 말로 토큰이 발행된 날짜와 시간을 의미합니다.
- exp : expiration을 줄인 말로 토큰 만료 시간을 의미합니다.
2-3. Signature
Signature는 토큰 자체의 진위여부를 판별하는 데이터로 토큰의 유효성 검사에 사용됩니다.
Header와 Payload를 합친 다음 Secrey Key(비밀키)로 Hash를 생성해서 암호화를 합니다. (비밀키는 절대 노출되면 안됨)
Secret Key(비밀키)는 나만 알고 있는 문자열, 비밀번호 같은 것입니다. 너무 간단하지 않으면 아무것이나 상관없습니다.
3. JWT 토큰을 활용한 사용자 인증 과정
사용자가 로그인을 성공하면 서버는 사용자 정보를 바탕으로 {헤더}.{페이로드} 부분을 작성하고
서버 자신의 비밀키를 이용해 {헤더}.{페이로드} 합친 부분을 암호화합니다. 암호화해서 나온 결과물인
{시그니처}를 {헤더}.{페이로드}.{시그니처}로 이어붙이고 Base64로 인코딩한 후 클라이언트에게 반환합니다.
그 이후 인증된 사용자만 접근할 수 있는 리소스를 조회하는 요청이 들어오면 서버는 일단
HTTP 헤더에 담겨있는 이 토큰을 Base64로 디코딩합니다. 디코딩해서 얻은 JSON을 {헤더}.{페이로드}와
{시그니처} 부분으로 나눕니다. 서버는 방금 디코딩해서 얻은 {헤더}.{페이로드}를 다시 합치고 자신의 비밀키로
암호화해서 새로운 {시그니처}를 생성합니다. 그 후 방금 {시그니처}와 HTTP 요청에 담겨온 {시그니처}를 비교해서
토큰의 유효성 검증을 합니다. 토큰이 일치하면 위조되지 않았다는 뜻입니다.
만약 누군가 {헤더}와 {페이로드}를 변경했다면 토큰이 일치하지 않았을 것입니다.
4. Key Rolling
JWT 생성 메커니즘을 보면 Secret Key(비밀키)가 노출되면 안된다는 걸 알 수 있습니다.
이런 문제를 방지하기 위해 여러 키를 생성해서 키 노출에 대비할 수 있습니다.
Secret Key(비밀키)를 여러개 생성해서 수시로 Secrey Key를 바꿔서 토큰을 생성해준다면
Secret Key가 1개 노출되어도 다른 키를 사용하는 토큰은 안전하겠죠
Key Rolling 방식에서는 여러개의 비밀키가 있으므로 해당 토큰이 어떤 비밀키로 암호화된건지 알 수 있는 식별정보가 있어야합니다. {Header} 파트에 "kid"에 이 비밀키 식별정보를 넣어둔다면 서버에서 토큰을 해석할 때 비밀키를 찾아서 {Signature}를 검증할 수 있습니다.
5. JWT 인증 VS 세션 인증
세션의 장점
- 사용자에게 노출되는 JSESSIONID는 유의미한 정보가 아닙니다. 서버 DB에 저장된 사용자 정보와 매핑시켜주는 식별 역할을 할 뿐이지 JSESSIONID 그 자체에는 아무런 정보가 없습니다.
- 때문에 JSESSIONID 탈취당하더라도 유저의 개인정보가 탈취당한 것이 아닙니다. (세션 하이재킹 공격을 당할 수 있기 때문에 절대적으로 안전하진 않음)
세션의 단점
- 확장성이 떨어집니다. 여러대 서버가 존재하는 분산 환경에서 각 서버가 세션을 공유하는데 어려움이 있습니다.
- 세션ID를 서버도 저장하기 때문에 별도의 저장공간이 필요합니다. (Refresh 토큰을 사용한다면 JWT도 마찬가지)
토큰의 장점
- 유저 정보가 토큰 자체에 저장되기 때문에 stateless한 방식으로 인증이 가능합니다. 때문에 클러스터&분산환경에서 확장성이 좋습니다.
토큰의 단점
- 한 번 발행된 토큰은 회수가 어렵습니다. 세션의 경우 세션을 서버에서 삭제하면 클라이언트가 JSESSIONID 보내도 아무런 의미가 없겠죠. 그러나 Access Token은 서버에 저장하지 않기 때문에 한 번 제공된 토큰은 회수할 수 없습니다. 그래서 보통 토큰 유효기간을 짧게 설정합니다.
- 토큰 자체에 유저 정보가 담겨있기 때문에 세션보다 안정성이 우려됩니다. 때문에 민간한 정보(비밀번호 등)는 토큰에 포함시키면 안됩니다.