1. 정의
JWT(JSON Web Token) : JSON 기반의 토큰 인증 방식
보안이 필요한 시스템에서 사용자 인증 및 정보를 안전하게 교환하거나
세션을 사용하지 않는 무상태(stateless) 인증 시스템 에서 주로 사용 - 별도의 세션 저장소 강제 X
2. 구조
JWT는 . 으로 구분된 세가지 부분으로 구성된다.
Header.Payload.Verify_Signature(서명)
- Header
- 서명 알고리즘 (HMAC SHA256, RSA 등을 사용)
- 토큰의 타입
- Payload
- Claim에 대한 property를 key-value 형태로 저장
- Claim은 일반적으로 사용자 정보 및 토큰에 대한 정보를 포함
- Claim에 대한 property를 key-value 형태로 저장
- Signature
- Header와 Payload를 조합한 후 비밀키를 이용해 해싱한 값
- 해당 서명으로 토큰 위변조 검증이 가능
2.1 더 깊게 알아보기
payload
표준 스펙상 key의 이름은 3글자로 정의
iss (Issuer) : 토큰 발급자
sub (Subject) : 토큰 제목 - 고유 식별자, 토큰 주체
aud (Audience) : 토큰 대상자
exp (Expiration Time) : 토큰 만료 시간
nbf (Not Before) : 토큰 활성 날짜 (이 날짜 이전 토큰은 활성화되지 않음을 보장)
iat ((Issued At) : 토큰 발급 시간
jti (JWT Id) : JWT 토큰 식별자 (iss가 여러명일때 구분 값)
필요에 따라 적절히 넣어서 토큰을 만듦
커스텀 Claim을 만들 수 있다.
{
"token_type" : "access_Token"
}
❗중요
payload와 header는 Base64로 인코딩 되었을 뿐 암호화는 되지 않았다.
이는 탈취하면 누구나 정보를 읽을 수 있다는 것이다. (base64 디코딩하면 끝..)
때문에 payload에는 식별값만 담아야한다.
Signature
비밀키는 보통 서버에서 256비트로 설정한다. 이것을 개인 키라고 한다.
서명은 서버에 있는 개인키로만 복호화가 가능하여 안전하다.
복호화 및 검증
클라이언트가 보낸 JWT 토큰( ex) Access_Token)이 유효한지 검증하려면
서버의 개인키로 Signature를 복호화 후,
그 안에 있는 base64UrlEncode(haeder)와 base64UrlEncode(payload)가
JWT토큰의 header와 payload 값과 일치하는지 확인하여 검증한다.
검증이 되었다면 인증을 허용하게 된다.
3. JWT의 한계와 극복
JWT의 한계
- 토큰의 크기에 따른 부하 : base64 인코딩으로 정보를 전달하기에 전달량이 많음
- payload 노출 : payload에는 암호화되지 않았기에 민감 정보 저장 불가
- 탈취 시 보안 위험 : 토큰 유출시 만료 전까지 악용 가능
- 갱신 어려움 : 세션과 달리 강제 만료가 어려움
JWT는 stateless 이기에 탈취당하면 만료될때까지 조치가 불가능하다.
토큰 자체에 만료시간이 정해져있고, 서버는 세션처럼 강제로 끊을 수 없다.
해결책
여기서 소셜로그인을 구현해봤거나 문서를 봤다면 한 번 쯤은 봤을 RefreshToken이 등장한다.
JWT토큰이 stateless 이면서 만료될때까지 조치가 불가능하다면 만료시간(exp)를 짧게 주면 된다.
이로써 최소한의 보안성을 보장할 수 있다.
하지만 서비스 이용자 입장에서는 짧은 유효시간으로 인해 재로그인을 반복해야한다.
이를 해결하기 위해 일반적으로 RefreshT oken을 사용한다.
Refresh Token
refresh 단어 뜻대로 새롭게 하다. 새로 고치다. 그 의미대로다.
JWT 토큰을 처음 발급할 때 Access Token 과 Refresh Token을 함께 발급하여
Access Token이 짧은 만료시간(exp)를 가져도 Refresh Token으로 재발급받을 수 있다.
흐름
1. 클라이언트가 로그인으로 인증 요청 , 서버는 확인 후 Access Token + Refresh Token 발급
2. 클라이언트는 Access Token을 가지고 원하는 자원을 서버에 요청 (Refresh Token은 잘 저장)
3. 요청 중 Access Token이 만료되면 서버가 오류를 반환
4. 클라이언트는 Access Token 만료를 인지하고, 보관했던 Refresh Token을 서버로 전달하여 새로운 Access Token 발급 요청
5. 서버는 Refresh Token을 받아 서버의 Refresh Token Storage에 해당 토큰이 있는 지 확인후 Access Token을 생성하여 반환
그런데 처음 JWT 발급할 때 Refresh Token Storage에 Refresh Token을 저장해야한다. 즉, 세션과 비슷하다.
하지만 탈취당했을 때 Refresh Token Storage를 초기화하여 탈취된 토큰이 더이상 재발급 받지 못하도록 막는 등 부가 기능이 생긴다.
Refresh Token Storage (클라이언트)
그렇다면 저장소는 어디로 해야할까?
1. local, session Storage
자바 스크립트로 토큰 값을 꺼내는 방식, 자바스크립트 ? XSS공격??? 안전하지 않다.
2. 쿠키 (HTTP Only)
쿠키도 자바스크립트 접근이 가능하므로 HTTP Only 옵션을 걸어주어야한다.
HTTPS가 적용되지 않은 이미지 등으로 인해 쿠키 탈취 위험이 있으므로 secure 옵션을 걸어야한다.
하지만 쿠키에 토큰을 담으면 CSRF(Cross-Site-Request Forgery) 공격에 취약
하지만 CSRF는 토큰을 직접 탈취하는 것이 아니므로
적절한 방어 대처(예: CSRF 토큰 사용, SameSite 설정 등)를 하면 안전하게 사용할 수 있음.
사용
2번 + SameSite 설정 || 2번 + CSRF 토큰
Refresh Token Storage (서버)
데이터베이스, Redis, 메모리 저장소에 저장
참고
- Refresh Token 탈취당했을 시 IP 바인딩, 단일 기기 사용 정책, 로그아웃 시 Refresh Token 폐기 등이 있다.
- HTTP Only : 브라우저에서 해당 쿠키 접근 불가 즉, 자바스크립트 접근 불가
- secure : HTTPS 에서만 쿠키 전송 가능
- CSRF : 크로스 사이트 요청 위조 , 사용자가 자신의 의지와는 무관하게 공격자가 의도하는 행위를 특정 웹사이트에 요청하게 하는 공격
- sameSite : 특정 도메인에서만 쿠키를 사용할 수 있도록 설정
sameSite 에 대한 자세한 설명은 여기를 참고해보자
쿠키의 SameSite 옵션이란? | 코드잇
쿠키의 옵션 중에서 SameSite 옵션에 대해 알아봅시다. | # 쿠키의 SameSite 옵션 쿠키에는 이 쿠키가 어떤 도메인에서만 사용할 수 있는지 설정할 수 있는 옵션이 있는데요. 바로 `Domain` 옵션과 `SameSit
www.codeit.kr
참조
https://velog.io/@ohzzi/Access-Token과-Refresh-Token을-어디에-저장해야-할까
'Back-End > Spring' 카테고리의 다른 글
filter 코드 리팩토링 해보기 (0) | 2025.04.07 |
---|---|
JDBCTemplate (0) | 2025.03.13 |