개념 정리
JWT(JSON Web Token)
: JSON 객체를 사용하여 정보를 안전하게 전송하기 위한 토큰
- 주로 사용자 인증 및 권한 부여 목적으로 사용된다.
JWT 예시
테스트 : https://jwt.io/ 에서 인코딩/디코딩을 해볼 수 있다.
- Encoded
- 토큰
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
- 토큰
- Decoded
- Header :
{"alg": "HS256", "typ": "JWT"}
- Header :
- Payload :
{"sub": "1234567890", "name": "John Doe", "iat": 1516239022} - Signature :
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT의 구조
JWT는 Headers + Payload + Signature로 구성되어 있다.
- Header : 헤더
- 해시 알고리즘과 토큰의 타입을 정의한다.
- Payload : 내용
- 전달하는 데이터(클레임) 를 포함한다.
- 클레임 : 주체 식별자(
sub), 사용자 이름(name), 토큰 발급시간(iat)로 구성되어 있다.
- Signature : 서명
- 헤더와 내용을 서버만 아는 비밀 키로 해싱한 값이다.
- 토큰의 무결성과 신뢰성을 보장한다.
위와 같이 헤더, 내용, 서명이 .을 구분자로 연결되어 있다.
JWT를 사용하는 이유
HTTP는 비상태성이라는 특성을 가지기 때문이다.
💡 비상태성(Stateless)
: 상태를 계속해서 보존하지 않는다.
클라이언트가 보내준 정보를 서버에 저장(유지)하지 않고, 보내준 정보를 기반으로 일련의 과정들을 처리후에 클라이언트에게 응답해주는 방식
vs Stateful
- Stateful은 같은 상태의 정보를 들고있는 상태의 서버가 지속되어야 한다. 여러 서버를 유지할 경우, 각 서버는 다른 정보를 가지고 있으며 이는 서버를 유지하기 힘들게 한다.
HTTP의 특징 중 하나
💡 HTTP의 특징?
- 무상태 프로토콜
- 비연결성
- HTTP 메시지 관련
이러한 HTTP 환경에서 서버는 사용자를 인가하기 위해서 세션 또는 토큰을 사용하여 문제를 해결한다.
💡 인증 vs 인가
- 인증 = 로그인 : 클라이언트가 자기자신이라고 주장하고 있는 사용자가 맞는지를 검증하는 과정
- 인가 : 인증 작업 이후에 행해지는 작업으로, 인증된 사용자의 자원에 대한 접근 확인 절차
세션 기반 인증 vs 토큰 기반 인증
세션 기반 인증

: 사용자의 인증 정보가 서버의 세션 저장소에 저장되는 방식
- 사용자가 로그인을 한다. (인증)
- 세션 데이터(인증 정보)를 서버 저장소에 저장한다.
- 사용자에게 저장된 세션 정보의 식별자인 Session ID를 발급한다.
- Session ID는 브라우저에 쿠키 형태로 저장되지만, 실제 인증 정보는 서버에 저장되어 있다.
- 이후 요청마다 HTTP Cookie헤더에 Session ID와 함께 서버로 전송한다.
- 서버는 요청을 전달받고, Session ID에 해당하는 세션 정보가 세션 저장소에 존재한다면 해당 사용자를 인증된 사용자로 평가한다.
토큰 기반 인증
: 인증 정보를 클라이언트가 직접 들고 있는 방식
- 사용자가 로그인을 한다. (인증)
- 인증 정보가 토큰의 형태로 브라우저의 저장소(로컬 스토리지/쿠키)에 저장한다.
- 사용자가 가지고 있는 토큰을 HTTP의 헤더에 담아 보낸다.
- 이 토큰을 서버는 위변조 되었는지(Signature로 판단), 만료 시간이 지나지 않았는 지 확인한다.
- 사용자 인증 정보를 확인해 사용자를 인가한다.
세션 기반 인증 vs 토큰 기반 인증
- 사이즈 : JWT는 발급 시각, 만료 시간, 토큰 ID 등 담겨 있는 정보가 세션 ID만 담겨있는 세션 방식보다 더 크다.
- 안정성과 보안문제 : 세션이 서버에서 관리하기에 보안 측면에서 더 유리하다.
- 세션ID가 해커에게 탈취당한다 하더라도, 서버에서 해당 세션을 무효처리하면 되지만 토큰은 서버가 트래킹하지 않고, 클라이언트가 모든 인증정보를 가지고 있어 속수무책으로 피해를 입을 수밖에 없다.
- 또한, JWT특성상 토큰에 실린 Payload가 별도로 암호화되어 있지 않으므로 누구나 내용을 확인해볼 수 있다.
- 확장성 : 불일치 문제를 해결하기 위해 Sticky Session, Session Clustering 등의 작업을 해주어야 하는 세션 방식과 달리 토큰 방식은 클라이언트가 저장하여 불일치 문제로부터 자유롭다.
Access Token과 Refresh Token
중간에 탈취 당하는 케이스를 고려하기 위해 Refresh Token과 Refresh Token을 구별한다.
- Access Token : API를 사용하기 위한 인증용 토큰
- 이 토큰이 만료되면 Access Token이 없는 것과 마찬가지이다.
- Refresh Token : Access Token의 유효기간 연장을 위한 토큰
- Access Token이 만료되면 Refresh Token을 이용하여 새로운 Access Token을 발급받는다.
즉, Access Token은 접근에 관여하는 토큰이고 Refresh Token은 재발급에 관여하는 토큰이라고 보면 된다.
JWT는 토큰 기반 인증으로, 세션과 다르게 stateless하다. 서버가 상태를 보관하고 있지 않다는 이야기이다. 서버는 한 번 발급한 토큰에 대해서 제어권을 가지고 있지 않다. 그래서, 토큰을 한 번 탈취당하면 해당 토큰으로 악의적인 행동을 할 수 있다. 이를 해결하기 위해서는 토큰 유효기간을 짧게 하는 방법도 있다. 하지만, 유효기간이 짧은 토큰 하나만 사용하면 매 번 사용자가 로그인을 해야 한다는 불편함이 있다. 이러한 불편함을 해결하기 위해서 등장한 것이 Refresh Token과 Access Token의 구분이다.
또한, Refresh Token은 서버에 저장함으로써 Refresh Token이 해커에 의해 탈취당했다고 판단되었을 때 서버에서 Refresh Token을 삭제함으로써 강제 로그아웃시킬 수 있다.
Refresh Token의 한계
- Access Token을 즉시 차단할 방법이 없다.
- 탈취된 Access Token이 유효한 그 짧은 시간 동안 악용되는 것을 막을 수 없다.
- Refresh Token을 탈취 당할 가능성
- 만약 Refresh Token자체를 탈취 당하면 해커는 마음껏 Access Token을 발행할 수 있다.
- 클라이언트는 이에 XSS, CSRF 공격으로부터 Refresh Token이 탈취되지 않도록 안전하게 보관해야한다.
Access Token을 무효화할 수 있게 만들면 안되나?
Access Token을 무효화할 수 있다는 것은, 이러한 토큰값을 DB에 저장하여 추적한다는 것을 의미한다.
그러나 토큰 기반 방식을 사용하는 이유는 stateless하다는 특징을 활용해, 확장성을 갖도록 경제성을 취하기 위함이다. 이러한 토큰을 DB에 저장한다는 것은 이러한 장점을 포기하는 것이다. 즉, 세션과 다를점이 없다.
사례 조사
1. 무신사
무신사에서는 위와 같이 쿠키에 사용자의 토큰을 저장하고 있다. 이 토큰을 jwt.io에서 디코딩을 해보자.
토큰 디코딩 해보기
💡 디코딩 (=Decoding, 복호화)
: 부호화된 정보를 부호화되기 전으로 되돌리는 처리
💡 인코딩 (=Encoding, 부호화)
: 사용자가 입력한 문자나 기호들을 컴퓨터가 이해할 수 있는 신호로 만드는 것
- Header에는 해시 알고리즘과 토큰의 타입이 적혀있다. 처음 예시에 있던 Header와 동일한 것을 확인할 수 있다.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- 그리고, 중간값인 Payload에는 나에 대한 정보가 담겨있다. 내 닉네임, 성별, 나이, 주문 건수 등등
- 마지막 Signature에는 헤더와 내용을 비밀 키로 해싱한 서명값이 담겨있다.
중요한 것은, 위의 값은 JWT를 사용하고 있지만, 이는 사용자의 인증을 위한 토큰이 아니라는 것이다. 해당 토큰을 제거했을 때, 로그인을 유지했다는 것을 통해 알 수 있었다.
위 app_atk 혹은 app_rtk를 제거했을 때 로그인이 해제되는 걸로 보아, 해당 값이 사용자 인증을 위한 토큰임을 알 수 있다. 형식을 보았을 때, .으로 header/payload/signature가 구분되어 있지 않는다는 것, 그리고 Expires에 세션이 적혀있어 세션 쿠키로 적혀있는 걸로 보아 무신사는 토큰 기반 방식이 아닌 세션 기반 방식의 사용자 인증을 처리하는 것을 알 수 있다.
💡 세션 쿠키 vs 지속 쿠키
- 세션 쿠키 : 세션이 종료되면 사라진다. 즉, 모든 브라우저를 종료하면 사라진다.
- 지속 쿠키 : expires에 명시된 기간이 종료되면 사라진다.
쿠키를 저장할 때, 만료기간을 설정하면 해당 만료기간을 가진 지속 쿠키가 되고, 설정하지 않으면 세션 쿠키가 된다.
무신사는 세션 쿠키 기반으로, command + Q를 눌러 모든 세션을 종료하면 해당 토큰이 사라져 로그인이 풀리게 된다.
❓ 피터, 모든 세션을 종료하면 세션 토큰이 제거되어, 로그인이 풀리는 건 알겠다. 근데, PC를 재실행하면 토큰이 살아있는데요 ?
여러 가지 상황을 재연해보며 알게된 점들이다.
맥 기준으로 종료할 때, 아래 옵션을 활성화한 후 PC를 종료하면 해당 브라우저가 그대로 살아나며 세션 스토리지의 데이터도 남아있는다.
![]()
위 옵션을 비활성화한 후 종료하면, 세션 스토리지 내용이 제거되어 재접속 시 로그인이 풀리게 된다. command + shift + T를 이용하여 이전 브라우저를 실행해도, 이전 접속 기록을 기반으로 실행하는 것이기에 세션 스토리지가 다시 생기지 않는다.
탭을 종료했을 때는 세션 스토리지가 남아있는다. 단축어로는 command + w가 있다.
브라우저를 종료했을 때는 세션 스토리지가 남아있지 않는다. 단축어로는 맥 기준 command + Q가 있고, 종료할 때 ‘Reopen’ 옵션을 활성화하지 않았을 때 이 경우에 해당한다.
정리하자면, 무신사는 세션 기반 인증 방식을 사용을 한다. 위의 app_atk와 app_rtk 둘 중 하나를 제거해도 로그인이 풀리는 것으로 보아, 둘 모두로 인증을 확인하는 듯하다. 이러한 세션 토큰에는 세션 ID가 담겨있어, 서버에서는 이 값으로 사용자 인증을 처리하는 방식인 것이다.
“무신사는 모든 페이지를 SSR로 받아온다.”
Next.js는 기본적으로 페이지 이동 간에는 CSR로 동작한다. 그러나 무신사는 다른 페이지로 이동할 때 항상 서버로부터 페이지를 받아온다.
이는, 모바일을 위해 웹뷰로 바꾼 무신사답게, 모바일을 배려한 것으로 보인다. PC였다면, 브라우저에서 렌더링하는 것이 보통 더 빨라서 페이지 이동간은 CSR로 동작하게 한다. 하지만, 무신사는 모바일 웹뷰를 배려해서, 서버에서 렌더링하여 모바일의 인터넷 속도 느림 등 영향을 최소화한 것이다.
흥미롭다. 다음에 한 번, 사이트 분석 본격적으로 해봐야겠다.
2. 트레바리
트레바리는 토큰 하나로 관리하며, 쿠키에 저장을 하고 있다.
그리고 또한, 사용자의 기본 정보를 받아오는 ‘boot’ api에서 payload에 담아서 보내고 있다.
다른 api에서는 headers의 Authorization에 토큰을 담아서 사용자의 인증을 처리한다.
정리
정리하면, 무신사는 세션 기반 인증을 하고 있고, 트레바리는 1개의 토큰으로 토큰 기반 인증을 하고 있다.
결론
- JWT는 JSON, key-value형태의 객체 자료구조를 가지고 있는 웹 토큰이다.
- JWT는 보통 사용자 인증 및 권한 부여를 목적으로 사용된다.
- JWT는 해시 알고리즘을 담은 Headers, 사용자 정보를 담은 Payload, 그리고 header와 payload의 변조 여부를 확인하기 위한 Signature로 구성되어 있으며 .으로 분리한다.
궁금증
Access Token과 Refresh Token을 구별하는 게 더 보안성에 있어서 유리할까? 그렇다면 무신사는 왜 이 둘을 구별하지 않을까?
Access Token과 Refresh Token
Access Token과 구별을 하는 것은 보안을 위해서이다. 토큰을 하나만 두었을 때, 주기가 짧을 경우 사용자가 자주 로그인을 해야 한다. 그리고, 주기가 길 경우, 보안에 취약하다. 토큰 특성 상 탈취 당하면, 서버에서는 조치를 할 수가 없다. 왜냐하면, 토큰을 서버 DB에서 저장하지 않기 때문이다. 이 방식은, 세션 방식에서 세션 불일치 문제를 해결하기 위한 stateless한 토큰 방식의 특징이다.
그렇기 때문에 Refresh Token과 Access Token 두 가지 유형의 토큰을 발급을 한다. 유효기간이 긴 Refresh Token을 계속해서 사용자가 가지고 있고, 유효기간이 짧은 Access Token의 유효기간(즉, 만료 기한)이 종료되면 Refresh Token을 이용해서 Access Token을 새로 발급받는 방식이다.
또한, 만약 사용자가 해킹을 당했다고 판단이 되면 Refresh Token을 무효화한다. 왜냐하면, Refresh Token은 서버에서 저장을 하고 Access Token은 저장하지 않기 때문이다.
Reference
https://hudi.blog/refresh-token/
https://velog.io/@moduri/Access-Token과-Refresh-Token
https://hudi.blog/self-made-jwt/