
JWT(JSON Web Token)는 JSON 객체를 이용해 정보를 안전하게 전송하기 위한 토큰 기반 인증 방식임. 토큰은 사용자의 인증 상태를 유지하고, 자원에 대한 접근 권한을 부여하기 위해 사용됨. JWT는 세션 상태를 서버에 유지할 필요 없이, 클라이언트가 보관하는 토큰만으로 인증을 처리할 수 있는 특징 덕분에 무상태(stateless) 인증 방식으로 매우 인기가 있음.
JWT는 3개의 부분으로 구성되며, 각각 Base64Url로 인코딩됨. 이 3개의 부분은 점(.)으로 구분되며, 일반적으로 다음과 같은 형식을 가짐.
<헤더(Header)>. <페이로드(Payload)>. <서명(Signature)>
헤더는 JWT의 타입과 서명 알고리즘을 지정하는 정보가 담겨 있음. 일반적으로 알고리즘은 HS256(HMAC SHA-256)이나 RS256(RSA SHA-256)과 같은 방식으로 사용됨.
예시:
{
"alg": "HS256",
"typ": "JWT"
}
페이로드는 JWT에 포함할 실제 정보가 담긴 부분으로, 클레임(Claim)이라는 형태로 데이터를 표현함. JSON(Key/Value) 형태의 한 쌍으로 이루어져 있음. 중요한 정보(예: 비밀번호)는 페이로드에 포함하지 않아야 함. 클레임 의 종류는 다음과 같이 크게 세 분류로 나뉘어져있음
등록된 클레임들은 서비스에서 필요한 정보들이 아닌, 토큰에 대한 정보들을 담기위하여 이름이 이미 정해진 클레임들임. 등록된 클레임의 사용은 모두 선택적임.
iss: 토큰 발급자 (issuer)
sub: 토큰 제목 (subject)
aud: 토큰 대상자 (audience)
exp: 토큰의 만료시간 (expiraton), 시간은 NumericDate 형식으로 되어있어야 하며 (예: 1516239022) 언제나 현재 시간보다 이후로 설정되어있어야함.
nbf: Not Before 를 의미하며, 토큰의 활성 날짜와 비슷한 개념임. 여기에도 NumericDate 형식으로 날짜를 지정하며, 이 날짜가 지나기 전까지는 토큰이 처리되지 않음.
iat: 토큰이 발급된 시간 (issued at), 이 값을 사용하여 토큰의 age 가 얼마나 되었는지 판단 할 수 있음.
jti: JWT의 고유 식별자로서, 주로 중복적인 처리를 방지하기 위하여 사용됩니다. 일회용 토큰에 사용하면 유용함.
{
"iss": "leedong617",
"exp": 1516239022,
}
공개 클레임은 사용자 정의 클레임으로, 공개용 정보를 위해 사용된다. 충돌 방지를 위해 URI 포맷을 이용함.
{
"https://velog.io/@leedong617/post/jwt": true,
}
사용자 정의 클레임으로, 클라이언트와 서버가 협의하에 임의로 지정한 정보를 저장해서 사용함.
공개 클레임과는 달리 이름이 중복되어 충돌이 될 수 있으니 사용할때에 유의해야함.
{
"username": "leedong"
}
예시:
{
"iss": "leedong617",
"exp": 1516239022,
"https://velog.io/@leedong617/post/jwt": true,
"username": "leedong"
}
서명은 헤더와 페이로드를 조합한 후, 서명 알고리즘과 비밀키를 사용하여 생성됨. 서명은 JWT의 무결성과 변조 방지를 위해 존재함. 클라이언트와 서버 간 전송 중 토큰이 변경되지 않았음을 보장함.
서명 생성 과정은 다음과 같음
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret) //your-256-bit-secret부분에 정해진 암호나 자신만의 암호를 넣으면 됨.
JWT의 최종 결과물은 다음과 같이 생성됨
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.
eyJpc3MiOiJsZWVkb25nNjE3IiwiZXhwIjoxNTE2MjM5MDIyLCJodHRwczovL3ZlbG9nLmlvL0BsZWVkb25nNjE3L3Bvc3Qvand0Ijp0cnVlLCJ1c2VybmFtZSI6ImxlZWRvbmcifQ
.
Zd0JrO7x5ddU3R3QfzyWZtr_QBytlc__eIPqTnfADBo
JWT는 서버가 세션을 유지할 필요 없이, 자체적으로 상태를 유지함. JWT 자체에 필요한 정보가 포함되어 있기 때문에, 서버가 별도의 상태 관리 없이 무상태(stateless)로 인증 처리가 가능함.
JWT는 서명(Signature)을 통해 토큰이 변조되지 않았음을 검증할 수 있음. 따라서 토큰이 변경되면 서버는 이를 감지하고 토큰을 무효화할 수 있음.
암호화가 아닌 서명을 통해 보안이 유지되므로, 기밀 정보는 페이로드(Payload)에 포함시키지 않음이 중요함.
JWT는 클라이언트가 발급받은 토큰을 HTTP 요청 헤더에 포함하여 서버에 요청을 보내는 토큰 기반 인증 방식임. 일반적으로 Authorization: Bearer <token> 형식으로 전송함.
JWT는 JSON 형식을 사용하므로, 플랫폼에 상관없이 대부분의 언어와 호환되어 다양한 클라이언트에서 사용할 수 있음.
클라이언트가 서버에 인증 요청을 보냄. (예: 사용자명/비밀번호)
서버는 요청을 확인하고, 사용자가 유효하다면 JWT를 생성하여 클라이언트에 반환함.
클라이언트는 서버로부터 받은 JWT를 저장함. (예: Local Storage, 쿠키)
클라이언트는 서버에 요청을 보낼 때, HTTP 헤더에 JWT를 포함하여 서버에 인증된 요청을 보냄.
서버는 헤더에 있는 JWT를 검증하고, 유효한 토큰일 경우 요청을 처리하고 필요한 자원에 접근하도록 허용함.
만료된 토큰이거나, 서명이 유효하지 않으면 서버는 요청을 거부함.
서버는 클라이언트로부터 JWT를 받으면 header, payload를 서버의 key값을 이용해 signature를 다시 만들고 이를 비교하여 일치했을 경우 통과시킴.
JWT토큰은 A(header) + B(payload) + C(signature)로 구성되어 있음.
JWT => A + B + C
만약 누군가 토큰의 내용이 담겨져 있는 payload를 수정하는경우 B'로 표기.
임의의 유저에 의해 수정된 JWT => A + B' + C
서버에 비밀키가 안전하게 보관되어 있으므로 위조가 되지 않았다면 두 서명은 일치하기 때문임.
만약 서버가 유저로부터 A+B'+C로 구성된 변조 토큰을 받았을때 서버는 A+B'로 C'를(변조된 서명) 만듬. 이후, 서버는 요청때 받은 토큰의 C와 서버가 만든 C'를 비교함.
이때 서버는 변조된 payload인 B'를 받았으므로 A+B'로 새로만든 서명은 C가 아닌 C'가 나올수밖에 없음. B'로 서명을 새로 만들었기 때문임.
따라서 C와 C'는 다르므로 이때 받은 요청은 서버가 신뢰할수 없다고 판단함.
JWT는 Base64로 암호화를 하기 때문에 디버거를 사용해서 인코딩된 JWT를 복호화하기 매우 쉬움. 복호화 하면 사용자의 데이터를 담은 Payload 부분이 그대로 노출되어 버림. 그래서 페이로드에는 비밀번호와 같은 민감한 정보는 넣지 말아야 함. 토큰의 진짜 목적은 '정보 보호'가 아닌, 해당 토큰이 신뢰 가능한 토큰인지 확인하는것 즉, '위조방지'가 목적임. 바로 위에서 소개했듯이, 시그니처에 사용된 비밀키가 노출되지 않는이상 데이터를 위조해도 시그니처 부분에서 바로 걸러지기 때문.
JWT는 무상태(stateless) 인증 방식이기 때문에, 서버가 상태를 저장하지 않는 RESTful API에서 매우 적합함. 클라이언트는 인증된 JWT를 전송하여 서버에 요청을 보내고, 서버는 이 토큰을 기반으로 인증 및 권한을 확인함.
SPA에서는 클라이언트가 서버로부터 한번 인증을 받은 후, JWT를 통해 페이지를 새로고침하지 않고도 인증 상태를 유지할 수 있음. 예를 들어 React나 Angular 같은 프론트엔드 프레임워크에서 자주 사용됨.
JWT는 토큰 기반으로 인증을 처리하기 때문에, 마이크로서비스 간 통신에서 유용하게 사용됨. 각 마이크로서비스가 독립적으로 인증을 관리할 필요 없이, JWT를 사용해 인증 상태를 공유하고 서비스 간 인증을 수행할 수 있음.
사용자의 권한과 인증 상태를 담고 있어, 자원에 대한 접근을 허용하기 위한 토큰.
주로 짧은 유효 기간을 가지며, 사용자가 서버에 자원 요청을 보낼 때마다 인증 정보로 사용됨.
짧은 유효 기간: Access Token은 수 분에서 수 시간 정도의 짧은 유효 기간을 가짐. 만료되면 더 이상 사용할 수 없고, 새로 발급받아야 함.
클라이언트에 저장: Access Token은 주로 클라이언트 측에서 저장되고, 자원에 접근할 때마다 전송됨. 일반적으로 Authorization 헤더에 Bearer <token> 형식으로 포함되어 전송됨.
무상태: 서버는 Access Token 자체만으로 인증을 처리하므로, 서버 측에서 세션을 유지할 필요가 없음. 이로 인해 서버 확장성이 높아지고, 무상태(stateless) 특성을 가짐.
사용자의 권한과 정보 포함: Access Token에는 사용자의 역할이나 권한과 같은 인증 관련 정보가 포함될 수 있음. 이 정보는 서버가 요청을 처리할 때 참고하여 접근 제어를 수행함.
사용자가 인증에 성공하면, 서버는 Access Token을 발급하여 클라이언트에 전달함.
클라이언트는 이후 서버에 자원을 요청할 때마다 Access Token을 포함하여 인증을 받음.
서버는 토큰을 검증하고, 유효한 경우 사용자의 요청을 처리하여 자원에 접근을 허용함.
Access Token이 만료되면, 클라이언트는 새로운 Access Token을 발급받기 위해 Refresh Token을 사용함.
Access Token을 재발급받기 위한 장기 토큰(긴 유효기간)으로, Access Token이 만료되었을 때 사용하여 새로운 Access Token을 서버에서 요청할 수 있음.
긴 유효 기간: Refresh Token은 수 일에서 수 주 또는 수 개월 정도의 긴 유효 기간을 가짐. Access Token과 달리, 상대적으로 긴 시간 동안 사용할 수 있음.
서버 측에서의 보관과 검증: Refresh Token은 보안성을 위해 주로 서버 측에서 보관하고 관리하는 것이 일반적임. 서버에서 발급된 Refresh Token 목록을 유지하거나, 데이터베이스(DB)에 저장하여 유효성을 검증할 수 있음.
재발급 용도: Refresh Token은 Access Token이 만료되었을 때만 사용되며, 사용자에게 새로운 Access Token을 부여하기 위한 목적으로 사용됨.
보안성: Refresh Token은 클라이언트의 기밀 정보로 간주되며, 보안성을 높이기 위해 세션 하이재킹 방지와 같은 보안 메커니즘을 적용함.
사용자가 Access Token을 사용하여 서버에 자원 요청을 보냄.
Access Token이 만료되었을 경우, 클라이언트는 Refresh Token을 이용해 새로운 Access Token을 요청함.
서버는 같이 보내진 Refresh Token을 쿠키나 DB에 저장된 값과 비교하여 Refresh Token의 유효성을 검증하고, 유효하다면 새로운 Access Token을 발급하여 클라이언트에 반환함.
클라이언트는 새로운 Access Token을 받아, 다시 자원 요청에 사용할 수 있게 됨.
Refresh Token 자체가 만료되거나 무효화되면, 클라이언트는 다시 인증 절차를 통해 새로운 Refresh Token을 발급받아야 함.
만일 사용자가 로그아웃할 경우 Refresh Token을 삭제하여 재사용이 불가능하도록 처리함.
| 특징 | Access Token | Refresh Token |
|---|---|---|
| 용도 | 자원 요청 시 인증용으로 사용 | Access Token이 만료되었을 때 재발급용으로 사용 |
| 유효 기간 | 짧음 (수 분 ~ 수 시간) | 긺 (수 일 ~ 수 개월) |
| 저장 위치 | 클라이언트 측 | 서버 측에 저장하는 것이 보안상 더 안전함 |
| 사용 빈도 | 모든 요청에 대해 사용 | Access Token이 만료될 때만 사용 |
| 보안 우려 | 짧은 유효 기간으로 인해 상대적으로 안전 | 기밀 정보로 간주, 보안 관리 필요 |
| 재발급 기능 | 불필요함 (자체적으로 Access Token을 재발급 불가) | 필요함 (새로운 Access Token을 발급) |
Access Token은 짧은 유효 기간을 가지므로, 클라이언트에서 보관하고 요청 시마다 전송되므로 세션 하이재킹 또는 중간자 공격에 취약할 수 있음. 이를 방지하기 위해 HTTPS를 사용하여 토큰이 안전하게 전송되도록 해야 함. Access Token은 Local Storage, 세션 스토리지, 브라우저 메모리, HTTP 쿠키 에 저장 될 수 있음.
Refresh Token은 보안상의 이유로 서버 측에서 보관하는 것이 권장됨. 이 경우, Refresh Token을 발급할 때 데이터베이스에 저장하거나, 블랙리스트 관리 등을 통해 유효성을 관리할 수 있음. Refresh Token은 보안성 때문에 클라이언트에 저장할 경우, HTTP-Only 쿠키에 저장하여 자바스크립트로 접근하지 못하도록 해야 함.
Refresh Token을 사용할 때마다 새로운 Refresh Token을 발급하고, 기존의 Refresh Token을 무효화함. 이 방법은 Refresh Token이 도난당하더라도 한 번만 사용할 수 있기 때문에 재사용 위험을 방지함.
HTTPS 사용: Access Token과 Refresh Token을 전송할 때는 반드시 HTTPS를 사용하여 전송 중 암호화함.
HTTP-Only 및 Secure 쿠키: 클라이언트에 저장할 경우 HTTP-Only 쿠키로 설정하여 자바스크립트에서 접근하지 못하도록 하고, Secure 속성을 사용하여 HTTPS 연결에서만 전송되도록 함.
만료 시간: Access Token의 만료 시간을 짧게 유지하고, Refresh Token의 유효 기간을 관리하여 보안성을 높임.
블랙리스트: 로그아웃 시 Refresh Token을 블랙리스트에 추가하여, 더 이상 사용되지 않도록 함.
IP와 사용자 에이전트 추적: Refresh Token 사용 시, IP 주소와 사용자 에이전트 정보를 추적하여 의심스러운 활동을 탐지하고, 토큰을 무효화함.