1. 토큰 이론
- 사용자 인증 확인 방법에는 두가지가 존재
- 서버 기반 인증 (세션 인증): Spring Security 기본 제공
- 토큰 기반 인증
- 토큰 기반 인증 특징
- 무상태성
- 서버에서 인증 정보(토큰)을 저장할 필요 X (서버에 데이터 유지는 자원 소비가 큼)
- 상태 관리: 클라이언트에서 사용자의 인증 상태를 유지하며 이후 요청 처리
- 서버 입장: Client의 정보를 저장/유지 X -> 완전한 무상태(Stateless)
- 확장성
- 무상태성의 영향 -> 서버 확장 시 상태관리 신경 X -> 서버 확장 용이
- 세션 기반: 각각의 API에서 인증
- 토큰 기반: 하나의 토큰으로 요청(Request)을 보냄
- 추가: 페이스북, 구글 등 토큰 기반 인증을 사용하는 다른 시스템에 접근 가능
이를 활용해 다른 서비스에 권한 공유 가능
- 무결성
- 토큰 발급 후 변경 X -> 토큰의 무결성 보장
- 토큰 발급 후 변경 X -> 토큰의 무결성 보장
- 무상태성
- Client 와 Server간의 토큰 교환
- (Client) 로그인 요청(Request): ID/PW를 서버에 전달하며 인증 요구
- (Server) 토큰 생성 및 응답(Response): 유효한 사용자(DB)인지 확인
- (Client) 토큰 저장: 서버에서 받은 토큰 저장
- (Client) 토큰 정보 + 요청(Request): 인증이 필요한 API에 접근할 때 토큰을 함께 전송
- (Server) 토큰 검증: 발급된 토큰이 유효한 지 확인
- (Server) 응답(Response): 유효한 경우 로직 처리 후 응답
2. JWT
- 서버가 서명한 목적 / 주장(Claims) 의 봉투
- HTTP Request(요청)의 헤더 중 Authorization 키 값에 "Bearer + JWT 토큰 값"을 넣어 전달하는 방식
- 구조 (header.payload.signature): .을 기준으로 헤더, 내용, 서명으로 나뉨
- Header: 토큰의 타입(typ)와 해싱 알고리즘 정보(alg)
- payload(Claims): 토큰과 관련된 정보, 내용의 한 덩어리(Claim) => Claim은 키값 한 쌍
- Claim의 종류
- 등록된 Claim: 토큰에 대한 정보
더보기"iss": 토큰 발급자
"sub": 토큰 제목
"aud": 토큰 대상자
"exp": 만료 시간, NumbericDate형식, 현재 시간 이후로 설정
"nbf": 활성 날짜 개념, NumbericDate형식, 이 날짜 지나기 전까지 토큰 처리 X
"iat": 발급된 시간
"jti": 고유 식별자, 일회용 토큰 - 공개 Claim: 공개되어도 됨, 충돌 방지 가능한 이름(보통 URI로 지음)
- 비공개 Claim: 공개 되면 안됨, Client - Server 간 통신에 사용
- 등록된 Claim: 토큰에 대한 정보
- Claim의 종류
- Signature: 조작/변경 확인하는 용도
- 헤더의 인코딩 값 + 내용의 인코딩 값 => 주어진 비밀 키를 사용해 해시값 생성
3. 토큰의 문제점
- (문제 1) 토큰 자체가 노출되어 토큰을 탈취한 Request인지 확인 할 수 없음
- (해결 1) 토큰에 유효기간(exp)를 적용
- (문제 2) 토큰의 유효기간이 너무 긴 경우: 그 기간동안 탈취자가 사용 가능
- (해결 2) 토큰의 유효기간을 짧게 적용
- (문제 3) 사용자의 입장에서 토큰의 유효기간이 짧아 매번 재접속, 불편
- (해결 3) 리프레시 토큰 이용
4. 액세스 토큰 vs 리프레시 토큰
- 액세스 토큰(Access Token) != 리프레시 토큰(Refresh Token)
- 리프레시 토큰: 액세스 토큰이 만료된 경우, 새로운 액세스 토큰을 발급하기 위한 토큰
- 리프레시 토큰의 유효기간 ↑ && 액세스 토큰의 유효기간 ↓
→ 공격자가 액세스 토큰을 탈취해도 짧은 시간(유효기간)이 지난 후 사용 X (안전)
- 리프레시 토큰의 유효기간 ↑ && 액세스 토큰의 유효기간 ↓
- 사용 방법(권장)
- 액세스 토큰: 요청(Request) 보낼 때 HTTP 헤더로 보냄
- 헤더명: Authorization: Bearer <access_token>
- 클라이언트에서는 메모리에만 보관 권장(LocalStorage는 XSS 취약)
- 리프레시 토큰: HttpOnly + Secure(+ SameSite) 쿠키로 저장
- 브라우저가 자동으로 전송, JS로 접근 불가(탈취 난이도 낮아짐)
- 액세스 토큰: 요청(Request) 보낼 때 HTTP 헤더로 보냄
5. 리프레시 토큰 회전(Refresh Token Rotation)
- jti: UUID v4(문자열) 또는 32바이트 랜덤(Base64)
- 리프레시 토큰 발급 시, jti 를 서버 저장소(DB)에 저장
- 새로 액세스 토큰 갱신 시, 들어온 리프레시 토큰의 jti가 서버 저장소에 존재해야 함(통과)
- (통과) 저장된 jti를 삭제(소프트)하고, 새로운 리프레시 토큰의 jti를 저장
- 서버 저장소에서 같은 리프레시 토큰이 두번 이상 쓰이면(즉, 이미 지워진 jti) 탈취 의심
→ 서버 저장소에 존재하는 해당 유저의 jti를 모두 무효화
- 로그아웃: 해당 사용자의 리프레시 jti를 모두 삭제(소프트)
→ 더이상 액세스 토큰 갱신 불가 - 저장공간(DB): 주요 필드는 jti(리프레시 토큰 식별자), username(유저이름), exp(만료시간), revoked(사용됨)
6. 액세스 토큰 & 리프레시 토큰 흐름
- (Client) 로그인 요청(Request): ID/PW를 body에 넣어 Server로 요청
- (Server) 회원 인증: DB에 저장되어 있는 회원인지 확인
- (Server) 액세스 토큰/리프레시 토큰 발급:
- 액세스 토큰은 Client에게 body로 전달
- 리프레시 토큰은 서버 저장소에 정보 저장 후, 헤더로 HttpOnly로 전달
→ 이제 리프레시 토큰이 필요한 경우, Client에서 credentials:'include' 옵션으로 전달하면 됨
- (Client) 응답 결과 저장: Server로부터 발급받은 엑세스 토큰을 메모리에 저장
- (Client) JWT 토큰이 필요한 API를 탈 때 요청의 헤더에 액세스 토큰을 포함해서 전달
- (Server) 액세스 토큰 유효기간 확인: 유효기간을 확인함 <필터 이용 추천>
- 유효기간 내: 해당 API의 서비스 로직 실행
- 유효기간 만료: 상태값 401을 보내며 Client에게 리프레시 토큰을 요청함
- (Client) 401을 받은 경우: 리프레시 토큰을 받아 액세스 토큰을 재발급하는 API로 리프레시 토큰 전달
- (Server) 리프레시 토큰 확인: 서버 저장소(DB)에 받은 리프레시 토큰의 jti를 기준으로 revoked가 false인지 확인
- revoked가 false인 경우: 해당 서버 저장소의 리프레시 토큰을 무효화시키고, 새로운 액세스 토큰과 리프레시 토큰을 발급(반복)
- revoked가 true인 경우: 탈취 가능성 존재, 예외 처리
- (Server) 결과를 Client로 전달
- (Client) 새롭게 발급 받은 토큰으로 메모리 업데이트 & 원래 타려던 API로 다시 요청보냄(반복)
'Spring boot' 카테고리의 다른 글
| JWT (JSON Web Token) - 실습 (0) | 2025.09.24 |
|---|---|
| [Spring Boot] TDD (Test-Driven Development) (1) | 2025.04.22 |