JSON Web Token
JWT(JSON Web Token)는 사용자 인증 정보를 JSON 형태로 담아 서명한 토큰입니다. Header, Payload, Signature 세 부분이 Base64로 인코딩되어 점(.)으로 이어진 하나의 문자열이 됩니다. 서버는 이 토큰의 서명만 검증하면 별도의 세션 저장소 조회 없이 요청자가 누구인지, 어떤 권한을 갖고 있는지 알 수 있습니다. 토큰 안에 만료 시간(exp), 발급자(iss), 사용자 식별자(sub) 같은 클레임이 들어 있어 토큰 하나가 인증과 인가 정보를 함께 운반합니다. 서명 알고리즘은 HMAC(대칭 키) 또는 RSA/ECDSA(비대칭 키) 중에서 선택할 수 있고, 비대칭 키를 쓰면 발급자와 검증자를 분리할 수 있습니다. 한번 발급된 JWT는 서버 측에서 임의로 무효화하기 어렵기 때문에 만료 시간을 짧게 잡고 Refresh Token을 별도로 운용하는 것이 일반적입니다.
▶아키텍처 다이어그램
점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
사용자가 로그인한 뒤 다음 요청을 보낼 때, 서버는 이 요청이 방금 로그인한 사람인지 아닌지를 다시 판단해야 합니다. 전통적인 방식은 서버가 세션 ID를 발급하고, 그 ID에 대응하는 사용자 정보를 메모리나 데이터베이스에 저장해 두는 것이었습니다. 서버가 한 대일 때는 문제가 없습니다. 하지만 서버가 여러 대로 늘어나면 세션을 어디에 두느냐가 문제가 됩니다. 모든 서버가 같은 세션 저장소를 공유하려면 별도의 인프라가 필요하고, 그 저장소가 병목이 되거나 장애 지점이 됩니다. 세션 조회 없이도 '이 요청자가 누구인지, 어떤 권한을 갖고 있는지'를 확인할 방법이 필요해졌고, 그 답이 서명된 토큰에 정보를 담아 클라이언트가 직접 들고 다니게 하는 접근입니다.
서버 세션 기반 인증은 웹 애플리케이션이 단일 서버에서 동작하던 시절의 표준이었습니다. 사용자가 로그인하면 서버 메모리에 세션을 만들고, 브라우저에 세션 ID 쿠키를 내려보내면 됐습니다. 그런데 서비스 규모가 커지면서 로드밸런서 뒤에 서버가 여러 대 놓이기 시작했고, 마이크로서비스 아키텍처에서는 하나의 요청이 여러 서비스를 거치게 됐습니다. 이 상황에서 '인증 상태를 중앙 저장소에 묻지 않고, 토큰 자체에 담아서 어디서든 검증할 수 있게 하자'는 아이디어가 힘을 얻었습니다. 2015년 RFC 7519로 표준화된 JWT는 이런 분산 환경에서의 인증 전파 문제를 해결하기 위해 등장했고, OAuth 2.0과 OpenID Connect의 토큰 형식으로 빠르게 채택됐습니다.
JWT는 점(.)으로 구분된 세 덩어리로 이뤄집니다. 첫 번째 덩어리인 Header에는 서명 알고리즘(예: HS256, RS256)과 토큰 타입이 들어갑니다. 두 번째 Payload에는 사용자 식별자, 만료 시간, 발급 시각, 권한 범위 같은 클레임이 JSON으로 담깁니다. 세 번째 Signature는 Header와 Payload를 비밀 키로 서명한 결과입니다. 서버가 토큰을 받으면 Header와 Payload를 같은 알고리즘으로 다시 서명해 보고, 그 결과가 Signature와 일치하는지 비교합니다. 일치하면 내용이 변조되지 않았다는 뜻이고, Payload 안의 만료 시간이 아직 유효한지만 추가로 확인하면 됩니다. 이 과정에서 데이터베이스 조회는 일어나지 않습니다. 주의할 점은 Payload가 암호화된 것이 아니라 Base64로 인코딩된 것뿐이라는 사실입니다. 누구나 디코딩해서 내용을 읽을 수 있으므로 비밀번호나 개인정보를 Payload에 넣으면 안 됩니다. JWT는 '읽지 못하게 하는 것'이 아니라 '고치지 못하게 하는 것'입니다.
JWT와 서버 세션은 둘 다 '로그인한 사용자를 이후 요청에서 식별한다'는 같은 문제를 풉니다. 차이는 상태의 위치입니다. 서버 세션은 인증 상태를 서버에 저장하고 클라이언트에는 세션 ID만 줍니다. JWT는 인증 정보 자체를 클라이언트가 들고 다니고 서버는 서명만 검증합니다. 서버 세션은 서버가 세션을 삭제하면 즉시 로그아웃 처리가 되지만, JWT는 한번 발급되면 만료 시간까지 서버가 일방적으로 무효화하기 어렵습니다. 그래서 즉각적인 세션 종료가 중요한 경우에는 세션 방식이 더 직관적이고, 여러 서버나 서비스가 별도 저장소 없이 인증을 공유해야 하는 환경에서는 JWT가 운영 부담을 줄여 줍니다. JWT가 만능은 아닙니다. 토큰 탈취 시 만료까지 악용될 수 있고, Payload에 너무 많은 정보를 넣으면 매 요청마다 큰 토큰을 실어 나르게 됩니다. 즉시 무효화가 필요하면 결국 블랙리스트 저장소를 두어야 하는데, 그러면 세션의 장점을 일부 포기하는 셈이 됩니다.
JWT는 서버 인스턴스가 여러 대이고 공유 세션 저장소를 두기 어려운 환경에서 가장 자연스럽게 들어맞습니다. 로드밸런서가 요청을 어느 서버로 보내든 토큰 서명만 검증하면 되니, sticky session 같은 우회 전략이 필요 없어집니다. 마이크로서비스 간 인증 전파에서도 자주 쓰입니다. API Gateway가 JWT를 검증한 뒤, 내부 서비스들이 같은 토큰의 클레임을 읽어 각자의 인가 판단을 내릴 수 있습니다. 실무에서 주의해야 할 점은 토큰 수명 관리입니다. 액세스 토큰의 만료 시간을 15분 이내로 짧게 잡고, 만료된 토큰을 갱신하는 Refresh Token을 별도로 운용하는 패턴이 일반적입니다. Refresh Token은 서버에 저장하고 관리하므로 필요할 때 회수할 수 있습니다. 또 한 가지, JWT를 브라우저에서 어디에 저장하느냐도 설계 결정입니다. localStorage에 넣으면 XSS 공격에 노출되고, httpOnly 쿠키에 넣으면 CSRF를 따로 막아야 합니다. 환경과 위협 모델에 따라 저장 위치를 선택해야 합니다.
더 깊게 보기
현재 페이지의 개념 설명을 본 뒤 공식 문서로 바로 이동합니다.