JWT 알아보기
안녕하세요? 블로그 제작기 다음 글을 쓰려고 했지만 그럴 때가 아닌 것 같습니다.
오늘 면접을 봤거든요. 저의 밑바닥을 이렇게 또 한번 느낍니다. 😇
면접 질문 중 제대로 답하지 못했던 것들이 있는데 관련해서 정리하며 공부를 해보려고 합니다. 그 중 하나가 'JWT'입니다.
분명 JWT에 대해 알고 개발한 건데 왜 면접 때만 되면 바보가 될까요? 그건 아마도 외웠기 때문이라고 생각합니다. 저는 이제 더 이상 외우기 싫어요. 까먹기도 싫고요. 그럼 바로 JWT에 대해서 배워보겠습니다.
JWT란
JSON Web Token의 약자입니다. 웹에서 인증과 정보 교환을 안전하게 하기 위해 만들어진 토큰 기반 인증 방식입니다.
간단히 말하면 서버가 유저에게 "너 이 사람 맞지?"하고 확인하고, 맞으면 정보를 담은 토큰을 줘서 유저가 어떤 작업을 요청할 때마다 그 토큰을 보고 인증하는 방식입니다.
JWT는 Header.Payload.Signature처럼 .으로 구분된 3가지 부분이 있습니다.
Header
각 부분마다 어떤 역할이나 특징이 있으니 구분이 지어졌을 것입니다.
Header는 토큰의 타입과 서명 알고리즘이 담겨 있는데요, JSON 형태로 작성되며 Base64Url로 인코딩 해 첫 번째 부분이 됩니다.
{
"typ": "JWT",
"alg": "HS256"
}
typ은 필수가 아니며 대부분 JWT를 구분하기 위해 명시합니다. JWT 외에 다른 토큰 타입도 존재할 수 있지만 JWT에서는 거의 쓰이지 않습니다.
서명 알고리즘은 JWT 서명을 만들 때 어떤 암호화 알고리즘을 썼는지 알려주는 부분입니다. 대표적으로 HS256, RS256, ES256이 있습니다. 이 부분도 차차 알아보겠습니다만 일단은 넘어갈게요.
Payload
실제로 담고 싶은 정보를 넣는 공간으로 claims라고 부릅니다. 이 Claim의 종류는 크게 Registered claims(미리 정의됨), Public claims(사용자가 정의할 수 있는 공개용 정보), Private claims(당사자들 간의 정보 공유를 위한 사용자 지정 정보) 3가지가 있습니다.
// Registered claims
{
"sub": "1234567890",
"name": "홍길동",
"role": "admin",
"iat": 1690000000
}
Signature
서버에서 Header + Payload를 특정 알고리즘으로 서명해서 생성합니다. 서명을 통해서 토큰의 변조 여부를 확인할 수 있습니다.
3가지 부분이 모두 합쳐지면 다음과 같아집니다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Iu2VnOyXkCIsInJvbGUiOiJhZG1pbiIsImlhdCI6MTY5MDAwMDAwMH0
.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT의 특징
JWT의 정의와 생김새를 알아봤으니 이제 특징을 알아보겠습니다.
자체적으로 정보를 담는다
JWT는 아까 말한 Payload 안에 정보를 담습니다. 이를 claims라고 한다고 했죠.
그렇기 때문에 서버가 세션을 유지할 필요가 없습니다. JWT는 토큰 자체가 어떤 정보를 가지고 있기 때문에 무상태 인증이 가능합니다.
하지만 이 말은 토큰에는 민감한 정보를 넣어서는 안된다는 말이 됩니다. 토큰은 변조를 방지할 뿐 암호화 된 것은 아니기에 최소한의 정보만 담아야 합니다.
URL-safe
JWT는 Base64Url 방식으로 인코딩됩니다. 이건 일반 Base64와 비슷하지만, URL에서는 문제가 되는 +나 /, = 같은 문자들을 안전한 문자로 변환하는 방식입니다.
(Base64: 0과 1로 이루어진 바이너리 데이터를 ASCII 문자로 바꿔 전송 가능한 텍스트로 인코딩하는 방식)
이를 통해 어디서든 깨지지 않은 상태로 안전하게 전달할 수 있습니다.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
서명 가능
JWT는 변조 방지를 위해 서명이라는 것을 포함합니다.
JWT의 Payload는 인코딩 되어 있을 뿐 암호화가 되어 있는 것은 아니므로, 누구나 내용을 볼 수 있습니다. 그렇기 때문에 만약 누군가 토큰을 마음대로 수정하면, 서버가 이를 바로 알아차릴 수 있어야 합니다. 이 역할을 하는 것이 바로 서명입니다.
서명은 Header + Payload + Secret Key를 기반으로 만듭니다.
먼저 JWT의 앞부분인 Header + . + Payload를 Base64Url로 인코딩합니다. 그리고 서명 알고리즘에 따릅니다.
아까 자세히 다루진 않았지만, 서명 알고리즘에는 크게 2가지 종류가 있습니다.
대칭키(서버와 클라이언트가 동일 키를 사용), 비대칭키(발급 서버는 개인 키, 검증 서버는 공개 키로 검증)입니다.
이 서명을 검증하는 과정은 아래와 같습니다.
-
클라이언트가 보낸 JWT에서 3가지 부분을 분리.
-
Header에 명시된 알고리즘과 키를 사용해 새로운 서명 생성.
-
2번에서 만든 서명과 JWT에 있는 서명(Signature)를 비교.
JWT의 사용 흐름
-
로그인 유저가 아이디와 비밀번호를 입력하면 서버가 이를 확인합니다.
-
서버 JWT 생성 인증이 성공하면 서버는 Access Token과 Refresh Token을 생성합니다. (둘다 JWT지만 저장소나 관리 방법이 다름)
-
클라이언트 저장 클라이언트는 Access Token을 로컬 스토리지나 세션 스토리지에 저장하고, Refresh Token은 보안이 필요한 경우 httpOnly 쿠키 등에 저장합니다.
-
요청 시 JWT 전송 클라이언트가 API 요청을 보낼 때 헤더(Authorization: Bearer
)에 Access Token을 포함시킵니다. -
서버 검증 및 처리 서버는 JWT의 서명을 확인하고 Payload를 검증하여 요청을 처리합니다.
-
Access Token 만료 시
- Access Token이 만료되면 서버는 요청을 거부합니다.
- 클라이언트는 저장된 Refresh Token을 서버에 보내 새 Access Token을 발급받습니다.
- 서버는 Refresh Token을 검증 후, 새로운 Access Token을 발급하고 클라이언트는 이를 저장합니다.
- 클라이언트는 새 Access Token으로 다시 API 요청을 진행합니다.
장점과 단점
JWT의 장점은 '토큰 자체가 정보'라는 것을 중점으로 생각하면 됩니다.
그렇기 때문에 서버가 무언갈 저장할 필요가 없어 무상태로도 인증이 가능하고, 서명만 확인하면 되기 때문에 인증이 빠릅니다.
단점도 똑같이 생각하면 됩니다.
토큰 자체가 정보이기 때문에 절대 민감한 정보를 직접 저장해서는 안됩니다. 또한 토큰이 커지면 헤더에 부담이 될 수 있기 때문에 인증에 필요한 최소한의 정보만을 담아야 합니다.
오늘은 면접 때 제대로 대답하지 못했던 JWT에 대해 정리했습니다.
이렇게 적어도 먼 훗날 또 제대로 설명하지 못하고 말겠지요... 하지만 그럴 확률을 최대한 줄이고 싶습니다. 여러 번 보다 보면 유창해지겠죠. 제발!