- JWT는 다음 3개의 부분으로 구성된다.
xxxxx.yyyyy.zzzzz
- 각 부분은 Base64로 인코딩되며, ‘.’ 으로 연결된 문자열이 최종 JWT이다.
부분 설명 예시
| Header | 토큰 타입과 서명 알고리즘 | { "alg": "HS256", "typ": "JWT" } |
| Payload | 토큰에 담을 데이터 (예: userId, email 등) | { "sub": "123", "email": "abc@example.com" } |
| Signature | 위 Header + Payload를 비밀 키로 서명한 값 | HMACSHA256(base64(header) + "." + base64(payload), secret) |
JWT는 어떻게 작동하나?

- 사용자가 로그인
- ex) 소셜 로그인(Kakao, Google) 성공 시
- 서버가 JWT를 발급
- 서버는 사용자 정보(ex: userId, email)를 담은 JWT를 서명(Signature)을 생성
- JWT를 만들 때 비밀 키(Secret Key)를 사용해 서명
- 비밀키는 밑에서 설명
- 프론트가 이 JWT를 저장
- localStorage, sessionStorage 또는 Cookie에 저장
- 이후 요청에 JWT 포함
- 매 요청 시 HTTP 헤더에 JWT를 포함
Authorization: Bearer <JWT> - 서버는 이 JWT를 검증하고 사용자 인증 완료
- JWT 안에 이미 userId가 들어있기 때문에 DB 조회 없이 인증 가능
비밀 키(서명 signature)의 역할
- 이 서명 덕분에 나중에 누군가 JWT를 위조했는지를 판별할 수 있다.
즉, 서버가 발급한 진짜 JWT인지 아닌지를 구별하는 핵심이 바로 이 비밀 키이다.
JWT는 다음과 같은 방식으로 만들어진다
JWT = Base64UrlEncode(Header) + "." + Base64UrlEncode(Payload) + "." + Signature
서명(Signature)는 다음과 같이 생성된다.
- 서버는 이 과정을 거쳐 토큰을 발급한다.
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey
)
서버는 비밀 키로 서명, 검증도 서버만 가능
단계 설명
| JWT 발급 | 서버가 비밀 키를 이용해서 토큰 서명(Signature) 생성 |
| JWT 전달 | 프론트에 JWT 전달 |
| JWT 인증 요청 | 프론트가 JWT를 Authorization 헤더로 서버에 보냄 |
| JWT 검증 | 서버가 비밀 키로 Signature가 위조되지 않았는지 확인 |
Spring boot에서는 보통 다음과 같이 관리를 한다.
# application.yml
jwt:
secret: MyVerySecretKeyThatIsLongEnough
expiration: 86400000 # 24시간
@Value("${jwt.secret}")
private String secretKey;
JWT는 서버가 비밀 키로 서명해서 만든 토큰이고, 그 비밀 키 덕분에 위조 방지와 인증 검증이 가능하다.
주의할 점
- Payload는 암호화되지 않음! (단순 인코딩이라서 열어볼 수 있음)
- 중요한 정보(비밀번호 등)는 절대 Payload에 넣지 말 것
- 비밀 키가 노출되면 서명을 위조할 수 있으므로 서버에서만 안전하게 관리
Access Token과 Refresh Token 개념 추가
Access Token과 Refresh Token이란?
- 곧 서버에서 발급해주는 JWT가 결국 Access Token이라고 보면 된다.
- Refresh Token은 그 AccessToken의 유효성을 "지속적으로 보장하고 보안성을 강화"하는 역할을 한다
구분 설명
| Access Token | JWT 형식으로 발급된 인증 토큰. 클라이언트가 API 요청 시마다 서버에 인증용으로 사용 |
| Refresh Token | Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위한 장기 토큰. 보통도 JWT이지만, UUID 형태일 수도 있음 |
JWT인 accesstoken은 유효기간이 있다
왜?
Access Token(JWT)은 노출(탈취)될 수 있는 위험이 있기 때문에, 피해를 최소화하기 위해 유효기간이 꼭 필요하다.
그 이유는
- 보통 localStorage, sessionStorage, 심지어 모바일 앱 내부 DB에 저장됨
- XSS, CSRF, 디컴파일링 등으로 탈취될 수 있는 위치에 있음
- 이 상태에서 유효기간이 없으면? → 토큰을 훔쳐간 해커가 평생 인증 가능
- Authorization 헤더로 전송 → 중간자 공격(MITM)에서 노출될 수 있음 (HTTPS 미사용 시)
- 탈취되어도 토큰 유효기간이 짧다면 해커는 오래 못 씀 → 피해 축소
- 서버는 Access Token을 기억하지 않음 → 탈취 감지 불가
- 따라서 주기적 만료만이 안전장치 역할
시나리오는 다음과 같다.
시나리오 Access Token 만료가 없으면 Access Token 만료가 있으면
| 해커가 localStorage에서 탈취 | 평생 사용자로 위장 가능 | 짧은 시간 뒤 만료 → 피해 최소화 |
| 중간자 공격으로 토큰 유출 | 모든 API 접근 가능 | 일정 시간 후 자동 차단됨 |
| 브라우저가 감염됨 | 자동 로그인 유지됨 | 주기적으로 재인증 필요 |
그러면 Access token의 유효기간은 짧다. 그러면 사용자가 계속 로그인을 자주해야되는 불편함이 생겨버림
이걸 해결하고자 Refresh Token 사용
Refresh Token 도입
- Access Token은 짧게: 15분
- Refresh Token은 길게: 7일~30일
- Access Token이 만료되면 → Refresh Token으로 재발급 요청
JWT는 어떤 해킹에서로부터 막아줄까?
해킹 시나리오 3가지를 기준으로 한 번 보자
1. 세션 탈취(Session Hijacking)
세션 방식 인증 구조부터 한 번 보면

여기서 세션 탈취가 어떻게 일어날까?

- 서버는 쿠키만 보고 판단하기 때문에 누가 보냈는지 식별이 불가
- 그래서 세션 기반 인증은 쿠키 탈취에 매우 취약
JWT를 사용했을 때 세션 탈취가 무의미한 이유는?
- 세션 방식은 서버가 사용자 상태를 기억해야한다
- 서버가 sessionId로 유저 상태를 “기억”하고 있어야함
- sessionId만 탈취되면 그 유저가 되어버림
- JWT는 서버가 사용자 상태를 기억하지 않아도 된다.
- 모든 인증 정보가 JWT 안에 들어 있고 서버는 검증만 하면 됨
- 서버 입장에서는 이 JWT가 유효한가?만 판단하면 됨
즉 서버는 상태를 기억하지 않기 때문에 세션을 탈취했다는 개념 자체가 존재하지 않음
2. 토큰 위조(Token Forgery)

JWT기반이면
- JWT에는 서버의 비밀 키로 서명된 Signature가 포함되어 있어서 누구도 위조 불가, 서명이 다르면 검증 실패
- 서버는 항상 서명을 검증하므로 해커가 사용자 ID를 바꿔도 무효
3. Replay Attack(재사용 공격)

- 해커가 refresh token을 탈취해서 서버에게 Accesstoken 발급 요청을 해서 해커가 그 Accesstoken으로 사용자처럼 행동할 수 있는 것임
이걸 해결하기 위해 서버는 다음과 같이 행동할 수 있다.
1. Refresh Token을 서버에 저장 (DB 또는 Redis)
- 토큰을 발급할 때 서버에 같이 저장
- 재요청 시 DB의 토큰과 일치하는지 비교
2. Refresh Token Rotation (회전 방식)
- 한 번 사용된 Refresh Token은 즉시 폐기
- 새 Refresh Token 발급하고 이전 건 무효화
- 이전 토큰이 다시 쓰이면 공격으로 간주 → 사용자 로그아웃, 강제 만료
3. 사용자, IP, UA(Device) 추적
- Refresh 요청이 다른 디바이스/IP/User-Agent에서 오면 차단 or 인증 요구
'프로젝트 > kkrap' 카테고리의 다른 글
| [Spring boot] JWT 관리 코드 분석 (2) | 2025.08.08 |
|---|---|
| JWT 기반 인증 도입 개선 문서 (2) | 2025.08.03 |
| 메인페이지 무한 스크롤 기능 (1) | 2025.07.30 |
| 커서 기반 페이지네이션이란? (1) | 2025.07.30 |
| 폴더 검색 기능 설계 문서(ElasticSearch) (3) | 2025.07.29 |