들어가며
서버 기반 인증의 문제점
처음에는 서버(세션) 기반 인증 방식을 많이 사용했어요.
사용자가 로그인하면 서버가 세션을 만들어서 저장해두고,
이후 요청 때마다 그 세션을 확인하는 방식이죠.
하지만 시스템 규모가 커지면서 문제가 생기기 시작했습니다.
- 사용자가 많아질수록
- 서버에서 관리해야 할 세션 데이터가 계속 늘어나니까 메모리 부담이 커졌어요.
- 게다가 서버를 여러 대 운영하게 되면,
- 각 서버마다 세션 정보를 동기화해야 하는 복잡한 문제도 생겼죠.
토큰 기반 인증의 등장
이런 한계점들 때문에 토큰 기반 인증 방식이 나오게 되었어요.
- 토큰에는 사용자 인증에 필요한 정보가 이미 들어있어서, 서버가 별도로 세션을 저장할 필요가 없습니다.
- 서버는 토큰이 유효한지만 검증하면 되니까 훨씬 가벼워졌어요.
- 그리고 어떤 서버에서든 동일한 방식으로 토큰을 확인할 수 있어서 확장성 문제도 해결되었죠.
서버(세션) 기반의 인증 시스템
어떻게 동작할까?
기존 인증 시스템은 서버가 사용자 정보를 직접 기억하고 관리하는 방식이에요.
- 사용자가 로그인하면 서버는 "아, 이 사람이 로그인했구나"하고 그 정보를 세션이라는 형태로 저장
- 이 세션은
- 메모리
- 디스크
- 데이터베이스
이런 방식의 서버를 Stateful 서버라고 불러요.
왜냐하면 클라이언트의 상태(누가 로그인했는지, 어떤 권한을 가지고 있는지 등)를 계속 기억하고 있기 때문입니다.

문제점들
소규모 시스템에서는 이런 방식이 아직도 잘 작동하고 있어요.
하지만 웹과 앱 애플리케이션이 발달하면서 몇 가지 심각한 문제들이 드러나기 시작했습니다.
1. 세션 관리의 부담
- 사용자가 로그인할 때마다 서버는 그 정보를 세션으로 저장
- 대부분 메모리(RAM)에 저장하는데, 로그인한 사용자가 많아질수록 서버의 메모리가 금세 가득 참
- 데이터베이스에 저장하는 방법도 있지만, 이것도 결국 DB에 부담
2. 확장성의 벽
- 사용자가 늘어나면 서버도 늘려야 함
- 여기서 큰 문제가 생겨요.
- A 서버에서 로그인한 사용자가 B 서버로 요청을 보내면 B 서버는 그 사용자의 세션 정보를 모릅니다.
- 이를 해결하려면 세션을 여러 서버에 분산시키는 복잡한 시스템을 만들어야 함
3. CORS 문제
- 세션 관리에 자주 사용되는 쿠키는 보안상의 이유로 단일 도메인과 그 서브도메인에서만 작동하도록 설계
- 그런데 요즘은 하나의 서비스가 여러 도메인을 사용하는 경우가 많잖아요?
- 예를 들어:
- 메인 사이트: `example.com`
- API: `api.example.com`
- 이런 환경에서 쿠키로 세션을 관리하는 건 매우 번거롭습니다.
- 예를 들어:
토큰 기반 인증으로의 전환
이런 문제들 때문에 개발자들은 새로운 해결책을 찾기 시작했고,
그 결과가 바로 토큰 기반 인증 시스템이에요.
- 서버가 사용자 상태를 기억할 필요 없이
- 토큰 자체에 필요한 정보를 담아서 주고받는 방식입니다.
토큰 기반의 인증 시스템
토큰 기반 인증은 완전히 다른 접근법을 취해요.
서버가 사용자 정보를 기억하는 대신, 인증받은 사용자에게 특별한 토큰을 발급해줍니다.
사용자가 서버에 요청할 때마다 이 토큰을 HTTP 헤더에 포함시켜서 보내면,
서버는 "아, 이 사람 인증된 사용자구나"하고 바로 알 수 있죠.
가장 큰 차이점은:
서버가 더 이상 사용자의 인증 정보를 저장하거나 세션을 유지하지 않는다는 거예요.
클라이언트에서 오는 요청만으로 모든 작업을 처리하기 때문에 Stateless구조를 가집니다.
토큰 인증은 어떻게 작동할까?
- 로그인 시도:
사용자가 아이디와 비밀번호로 로그인을 해요 - 정보 검증:
서버에서 그 정보가 맞는지 확인합니다 - 토큰 발급:
정보가 정확하다면 서버가 Signed 토큰을 만들어서 사용자에게 줘요
(Signed라는 건 "이 토큰은 우리 서버에서 정식으로 만든 거야"라는 서명이 있다는 뜻이에요) - 토큰 보관:
클라이언트가 이 토큰을 저장해두고, 서버에 요청할 때마다 HTTP 헤더에 포함시켜서 보냅니다 - 검증과 응답
서버는 토큰이 유효한지만 확인하고 요청에 응답해요
토큰 방식의 강력한 장점들
1. 무상태성과 확장성의 완벽한 해결
- 토큰은 클라이언트가 가지고 있으니까 서버는 완전히 Stateless해져요.
- 어떤 서버든 토큰만 확인하면 됩니다.
- 서버를 10대, 100대로 늘려도 전혀 상관없어요.
- 사용자가 A서버에서 로그인했다가 B서버로 요청을 보내도 문제 없습니다.
2. 보안성 향상
- 더 이상 쿠키를 사용하지 않으니까,
- 쿠키 관련 보안 취약점들이 사라져요.
- 물론 토큰 자체의 보안은 신경 써야 하지만,
- 전반적으로 더 안전한 구조가 됩니다.
3. 확장성(Extensibility) - 권한의 유연한 관리
- 토큰에 선택적인 권한만 넣어서 발급할 수 있어요.
"이 토큰으로는 프로필만 볼 수 있고, 수정은 안 돼"
이런 식으로 세밀하게 제어가 가능합니다.
- OAuth를 사용하면,구글이나 페이스북 계정으로 다른 웹서비스에 로그인하는 것도 쉽게 구현할 수 있어요.
4. 플랫폼과 도메인의 자유로움
- 세션 기반 인증의 골칫거리였던 CORS 문제가 완전히 해결됩니다.
- 웹사이트든 모바일 앱이든, 어떤 도메인이든 상관없이
- 토큰만 있으면 인증이 가능해요.
JWT란?
JWT는 JSON Web Token의 줄임말로,
JSON 포맷을 사용해서 사용자 정보를 안전하게 전달하는 토큰이에요.
가장 중요한 특징은Self-Contained 방식이라는 점입니다.
이게 무슨 뜻이냐면,
토큰 자체에 필요한 모든 정보가 들어있다는 거예요.
마치 신분증처럼 그 자체로 완전한 정보를 담고 있죠.
- 주로 회원 인증이나 서비스 간 정보 전달에 사용되는데,
- 앞서 설명한 토큰 기반 인증의 대표적인 구현체라고 볼 수 있어요.
JWT의 구조
- Header
- Payload
- Signature
각 부분은 JSON 형태로 되어 있고 , `Base64Url`로 인코딩된 후 점(.)으로 연결됩니다.
💡 여기서 중요한 건:
Base64Url은 "암호화"가 아니라 "인코딩"이라는 점이에요.
같은 내용이면 항상 같은 결과가 나옵니다.

Header(헤더)
토큰의 신분증
헤더에는 두 가지 핵심 정보가 들어있어요:
{
"alg": "HS256",
"typ": JWT
}
- `typ`: 이 토큰이 JWT라는 타입 정보
- `alg`: 서명을 만들 때 사용할 알고리즘 (예: HS256, RSA 등)
여기서 주의할 점은 `alg`가 헤더를 암호화하는 게 아니라,
나중에 서명(Signature)을 만들 때 사용할 해싱 알고리즘을 지정하는 거라는 점이에요.
PayLoad(페이로드)
실제 데이터가 담긴 곳
페이로드에는 클레임(Claim)이라는 정보 조각들이 들어있어요.
클레임은 세 종류로 나뉩니다:
등록된 클레임(Registered Claim)
JWT 표준에서 미리 정해둔 클레임들이에요.
모두 선택사항이지만, 사용을 권장해요.
키가 모두 3글자로 되어있어 토큰이 간결
- `iss`: 토큰을 발급한 곳
- `sub`: 토큰의 주제 (보통 사용자 이메일 등 고유값)
- `aud`: 토큰을 사용할 대상
- `exp`: 언제까지 유효한지 (만료시간)
- `nbf:` 언제부터 사용 가능한지
- `iat:` 언제 발급되었는지
- `jti`: 토큰의 고유 ID (중복 방지용)
공개 클레임(Public Claim)
누구나 사용할 수 있는 사용자 정의 클레임이에요.
다른 사람과 충돌하지 않도록 URI 형식을 사용해요:
{
"<https://mangkyu.tistory.com>": true
}
비공개 클레임(Private Claim)
서버와 클라이언트가 서로 약속해서 사용하는 클레임이에요:
{
"token_type": access
}
Signature(서명)
토큰의 진위를 보장하는 부분
서명은 토큰이 위조되지 않았음을 증명하는 핵심 부분이에요.
- `Header`와 `Payload`를 각각 `Base64Url`로 인코딩
- 인코딩된 값들을 비밀 키와 함께 `Header`에서 지정한 알고리즘으로 해싱
- 해싱된 결과를 다시 `Base64Url`로 인코딩
이 과정을 통해 만들어진 서명 덕분에,
누군가 Payload의 내용을 바꾸면
👉 서명이 맞지 않게 되어 변조를 바로 알 수 있어요.
JWT 토큰 예시
점(.)으로 구분된 세 부분을 볼 수 있죠?
- 첫 번째가 ➝ `Header`
- 두 번째가 ➝ `Payload`
- 세 번째가 ➝ `Signature`예요.

HTTP 요청에서 JWT 사용하기
실제 API 통신할 때는 이렇게 사용해요:
{
"Authorization": "Bearer {생성된 토큰 값}",
}
`Bearer`라는 접두사를 붙이는 게 일반적인 관례입니다.
"이 토큰을 가진 사람이 요청하는 거야"라는 의미죠.
JWT 단점 및 고려사항
JWT가 완벽한 해결책은 아니에요.
몇 가지 중요한 단점들이 있어서 사용할 때 주의깊게 고려해야 합니다.
1. Self-contained의 양면성
- `토큰 자체에 정보`가 들어있는 게 장점이기도 하지만 단점이기도 해요.
- 토큰에 많은 정보를 넣으면 편리하지만, 그만큼 토큰이 커지고 보안 위험도 증가하죠.
2. 토큰 길이 문제
- `Payload`에 클레임을 많이 넣을수록 토큰이 길어져요.
- 사용자 정보, 권한, 만료시간 등을 다 넣다 보면 토큰이 꽤 커질 수 있어요.
- 매번 API 요청할 때마다 이 긴 토큰을 보내야 하니까 네트워크 부하가 생길 수 있습니다.
3. Payload 보안 취약점
- `Payload`는 암호화가 아니라 단순한 `Base64Url` 인코딩이에요.
- 누군가 토큰을 가로채서 디코딩하면 안의 내용을 다 볼 수 있다는 뜻이죠.
따라서, Payload에는 절대 비밀번호나 개인정보 같은 민감한 데이터를 넣으면 안 돼요.
정말 중요한 데이터는 JWE(JSON Web Encryption)로 암호화하거나
아예 토큰에 포함시키지 않아야 합니다.
4. Stateless의 함정
- JWT는 상태를 저장하지 않는다는 게 장점이지만, 이것 때문에 생기는 문제도 있어요.
- 한번 발급된 토큰은 서버에서 임의로 무효화할 수 없어요.
예를 들어: 직원이 퇴사했는데 토큰이 아직 유효하다면? 서버 입장에서는 그 토큰을 강제로 삭제할 방법이 없어요.
그래서 토큰에는 반드시 만료시간(`exp`)을 설정해야 하고, 보통 짧게 설정하는 편이에요.
5. 토큰 저장의 고민
토큰을 클라이언트에서 관리해야 하는데, 어디에 저장할지가 고민이에요.
- `localStorage`: 편리하지만 XSS 공격에 취약
- `cookie`: HttpOnly 설정으로 보안은 좋지만 CSRF 공격 위험
- `memory`: 가장 안전하지만 새로고침하면 사라짐
각각 장단점이 있어서 상황에 맞게 선택해야 해요.
결국 JWT는:
강력하고 유용한 도구지만,
그 특성을 잘 이해하고 적절한 보안 조치를 함께 사용해야
안전하고 효율적인 인증 시스템을 만들 수 있어요.
JWT의 이중 보안: Access Token과 Refresh Token
JWT 하나만 사용하면 간단하긴 하지만, 보안상 위험해요.
만약 토큰이 해커에게 탈취당하면,
토큰이 만료될 때까지 서버는 그것이진짜 사용자인지 가짜인지 구분할 수 없습니다.
그래서 현업에서는
`Access Token`과 `Refresh Token`
두 개의 토큰을 함께 사용하는 전략을 택해요.
💡 중요한 건:
둘 다 똑같은 JWT라는 점이에요.
단지 어떻게 저장하고 관리하느냐의 차이일 뿐이죠.
Access Token: 실제 업무를 담당하는 토큰
역할과 특징
`Access Token`은 클라이언트가 가지고 있는
"실제 근무용 출입카드" 같은 존재예요.
- 사용자의 실제 정보와 권한이 담겨 있어서
- 서버에 API 요청을 할 때마다 이 토큰을 보여주면
- 서버가적절한 응답을 해주죠.
"아, 이 사람 맞구나" 하고
보안을 위한 짧은 수명
`Access Token`의 핵심 특징은 짧은 유효기간입니다.
보통 15분~1시간 정도로 설정해요.
왜 이렇게 짧게 할까요?
만약 토큰이 해킹당해도 금세 만료되니까
👉 피해를 최소화할 수 있거든요.
Refresh Token: 새로운 열쇠를 만들어주는 토큰
`Refresh Token`은 "새 출입카드를 발급받기 위한 증명서" 같은 역할이에요.
- `Access Token`이 만료되면
- 이 토큰을 사용해서 새로운 `Access Token`을 발급받는 거죠.
- 사용자가 매번 로그인할 필요 없이
- 계속 서비스를 이용할 수 있게 해주는 핵심 장치예요.
동작 방식
- 로그인하면 서버에서 두 토큰을 모두 발급
- 서버는 `Refresh Token`을 데이터베이스에 저장,
- 클라이언트는 두 토큰 모두 저장
- `Access Token`이 만료되면
- `Refresh Token`으로 새 `Access Token`을 발급
- 로그아웃 시
- 서버에서 `Refresh Token`을 삭제해서
- 재발급을 차단
실제 예시
- `Refresh Token` 유효기간: 2주
- `Access Token` 유효기간: 1시간
사용자가 1시간 후 API를 요청하면
`Access Token`은 만료되지만,
`Refresh Token`이 유효하니까
새로운 `Access Token`을 받을 수 있어요.
그리고 2주가 지나면
- 다시 로그인해야 합니다.
Access/Refresh Token 재발급 원리
초기 설정
- 로그인하면 두 토큰 모두 발급
- `Refresh Token`만 서버 DB에 저장
- 클라이언트는 두 토큰을 쿠키나 웹스토리지에 저장
토큰 검사 시나리오
사용자가 인증이 필요한 API에 접근할 때,
서버는 토큰 상태를 확인하고 각 경우에 맞게 처리해요:
- Case 1: 둘 다 만료
→ ❌ 에러 발생, 재로그인 필요 - Case 2: `Access Token` 만료, `Refresh Token` 유효
→ ✅ 클라이언트의 `Refresh Token`과 DB의 `Refresh Token`을 비교해서 일치하면- 새 `Access Token` 발급
- Case 3: `Access Token` 유효, `Refresh Token` 만료
→ ✅ `Access Token`이 유효하다는 건 인증된 사용자라는 뜻
- 새 `Refresh Token` 발급
- Case 4: 둘 다 유효
→ ✅ 정상 처리
로그아웃
→ `Access Token`과 `Refresh Token` 모두 만료시켜요
Refresh Token 인증 과정 상세
최초 로그인 과정
- 사용자가 ID, 비밀번호로 로그인
- 서버에서 회원 DB와 비교해서 검증
- 로그인 성공 시
- `Access Token`, `Refresh Token` 발급
- 회원 DB에 `Refresh Token` 저장
- 사용자는 `Refresh Token`을 안전한 곳에 저장하고,
- `Access Token`으로 API 요청
정상 동작
- `Access Token`을 헤더에 포함해서 API 요청
- 서버에서 토큰 검증 후 적절한 데이터 응답
토큰 만료 처리
- 8간이 지나서 `Access Token` 만료
- 사용자가 기존처럼 만료된 `Access Token`으로 요청
- 서버가 토큰 만료 확인 후 → 권한 없음 응답
토큰 재발급
- 사용자가
- `Refresh Token`과 (만료된) `Access Token`을 함께 서버로 전송
- 서버에서
- `Access Token`이 위조되지 않았는지 확인
- `Refresh Token`을 DB와 비교
- 모든 검증이 통과되면
- 새로운 `Access Token` 발급해서 응답

출처:
https://mangkyu.tistory.com/56
https://inpa.tistory.com/entry/WEB-📚-JWTjson-web-token-란-💯-정리
https://inpa.tistory.com/entry/WEB-📚-Access-Token-Refresh-Token-원리-feat-JWT