안녕하세요! 오늘은 웹 서비스의 인증 보안에 필수적인 JWT, AccessToken, RefreshToken에 대해 자세히 알아보겠습니다. 스프링 환경에서 Redis를 활용한 구현 방법도 함께 살펴볼게요.
1. JWT vs 세션: 인증 시스템의 두 가지 접근법
세션 기반 인증
세션 인증은 전통적인 방식으로, 서버가 사용자의 상태를 직접 관리합니다.
작동 방식:
- 사용자가 로그인하면 서버는 세션 ID를 생성하고 서버 메모리나 DB에 저장
- 세션 ID는 쿠키를 통해 클라이언트에 전달
- 클라이언트는 요청 시마다 쿠키의 세션 ID를 함께 전송
- 서버는 이 ID를 검증하여 사용자 인증
장점:
- 서버에서 세션을 완전히 제어 가능
- 세션 만료, 삭제 등의 작업이 서버 측에서 쉽게 처리됨
단점:
- 서버에 상태 저장 필요(stateful)
- 사용자가 많아지면 서버 부하 증가
- 분산 시스템에서는 세션 공유를 위한 추가 설정 필요
JWT(JSON Web Token) 기반 인증
JWT는 클라이언트 측에 정보를 안전하게 저장하는 토큰 기반 인증 방식입니다.
작동 방식:
- 사용자 로그인 시 서버는 사용자 정보와 권한을 포함한 JWT 생성
- 토큰은 비밀키로 서명되어 클라이언트에 전달
- 클라이언트는 요청 시마다 JWT를 헤더에 포함시켜 전송
- 서버는 토큰 서명을 검증하여 사용자 인증
장점:
- 서버에 상태 저장 불필요(stateless)
- 확장성이 높음(서버 확장 용이)
- 모바일 애플리케이션, 웹 API에 적합
단점:
- 토큰에 포함된 정보는 변경 불가(발급 후 취소 어려움)
- 토큰 크기가 세션 ID보다 큼
- 보안에 더 많은 주의 필요
2. 엑세스 토큰(Access Token)은 무엇인가?
AccessToken은 보호된 리소스에 접근하기 위한 임시 권한 증명서입니다.
핵심 특징:
- 짧은 유효 기간: 일반적으로 몇 분에서 몇 시간 내 만료
- 권한 정보 포함: 사용자 식별자와 권한 정보가 담김
- 서명 검증: 서버의 비밀키로 서명되어 위변조 방지
JWT 형태의 AccessToken 구조:
- Header: 암호화 알고리즘, 토큰 타입 정보
- Payload: 사용자 ID, 권한, 만료 시간 등
- Signature: 헤더와 페이로드를 비밀키로 서명한 값
사용 목적:
- 인증된 사용자의 API 요청 승인
- 최소 권한의 원칙 적용(필요한 권한만 포함)
- 짧은 만료 시간으로 보안 위험 최소화
3. RefreshToken과 함께 사용하는 이유
AccessToken만 사용하면 두 가지 딜레마에 빠집니다: 짧은 유효기간은 보안에 좋지만 사용자 경험이 나빠지고, 긴 유효기간은 사용자 경험은 좋지만 보안이 취약해집니다. 이 문제를 해결하기 위해 RefreshToken을 도입합니다.
RefreshToken의 필요성:
- 보안과 사용자 경험의 균형: AccessToken은 짧게 유지하면서도 사용자가 자주 로그인할 필요가 없음
- 무결성 보호: 민감한 권한 정보는 AccessToken에만 포함하고 RefreshToken은 단순 식별 용도
- 토큰 순환: 주기적인 AccessToken 갱신으로 탈취된 토큰의 사용 시간 제한
AccessToken과 RefreshToken의 차이:
특성AccessTokenRefreshToken
유효기간 |
짧음(15분~2시간) |
김(1주~몇 개월) |
저장위치 |
클라이언트 메모리 |
쿠키(HttpOnly, Secure) |
포함정보 |
사용자 ID, 권한 |
주로 사용자 ID만 |
사용빈도 |
모든 API 요청마다 |
AccessToken 갱신 시에만 |
보안중요도 |
중간 |
매우 높음 |
4. 동작 과정: 스프링에서 Redis 활용하기
인증 시스템 구현 과정
- 초기 인증 (로그인)
- 사용자가 아이디와 비밀번호로 로그인 요청을 보냅니다.
- 서버는 사용자 정보를 검증한 후 인증에 성공하면:
- AccessToken 생성: 짧은 유효기간을 가진 JWT를 생성합니다.
- RefreshToken 생성: 긴 유효기간을 가진 토큰을 생성합니다.
- Redis에 RefreshToken 저장: 사용자 ID를 키로 하고 RefreshToken을 값으로 Redis에 저장합니다. 이때 토큰의 만료 시간도 함께 설정합니다.
- 생성된 AccessToken은 응답 본문에 담아 클라이언트에게 전송합니다.
- RefreshToken은 HttpOnly, Secure 속성이 설정된 쿠키로 클라이언트에게 전송합니다.
- API 요청 및 AccessToken 검증
- 클라이언트는 보호된 리소스에 접근할 때마다 Authorization 헤더에 AccessToken을 포함시켜 요청합니다.
- 서버의 인증 필터는 요청에서 토큰을 추출하고 유효성을 검증합니다.
- 토큰이 유효하면 Spring Security의 SecurityContextHolder에 인증 정보를 설정하고 요청을 처리합니다.
- 토큰이 유효하지 않거나 만료된 경우 401 Unauthorized 응답을 반환합니다.
- 토큰 갱신 프로세스
- AccessToken이 만료되면 클라이언트는 토큰 갱신 엔드포인트로 요청합니다.
- 서버는 쿠키에서 RefreshToken을 추출합니다.
- 추출한 RefreshToken이 유효한지 검증합니다.
- Redis에 저장된 해당 사용자의 RefreshToken과 비교합니다.
- 두 토큰이 일치하면:
- 새로운 AccessToken을 생성합니다.
- 생성된 AccessToken을 클라이언트에게 응답으로 전달합니다.
- 일치하지 않으면 401 Unauthorized 응답을 반환합니다.
- 로그아웃 처리
- 사용자가 로그아웃을 요청하면:
- Redis에서 해당 사용자의 RefreshToken을 삭제합니다.
- 현재 사용 중인 AccessToken을 블랙리스트에 추가합니다.
- 블랙리스트는 Redis에 "blacklist:토큰값" 형태의 키로 저장되며, 만료 시간은 해당 토큰의 남은 유효 시간으로 설정합니다.
- RefreshToken 쿠키를 제거합니다.
Redis의 역할
Redis는 토큰 기반 인증 시스템에서 여러 중요한 역할을 수행합니다:
- RefreshToken 저장소
- 키: 사용자 식별자(username)
- 값: RefreshToken 문자열
- TTL: RefreshToken 만료 시간
- 로그아웃된 AccessToken 블랙리스트
- 키: "blacklist:" + AccessToken
- 값: "logout"
- TTL: 토큰 잔여 만료 시간
- 분산 환경 지원
- 여러 서버 인스턴스가 동일한 Redis를 참조하여 인증 상태 공유
- 서버 확장성 향상
- 토큰 순환 관리
- RefreshToken 재사용 감지 및 방지
- 모든 사용자의 토큰 일괄 만료 처리 가능
5. 토큰 탈취 방지 전략
클라이언트 측 보안
- 적절한 토큰 저장
- AccessToken: 메모리(JavaScript 변수)에 저장, localStorage 사용 지양
- RefreshToken: HttpOnly + Secure 쿠키에 저장(JavaScript 접근 불가)
- HTTPS 사용 필수
- 모든 API 통신은 SSL/TLS로 암호화
- HSTS(HTTP Strict Transport Security) 헤더 적용
- XSS 방어
- 사용자 입력 데이터 적절한 이스케이프 처리
- CSP(Content Security Policy) 헤더 설정
서버 측 보안
- 짧은 AccessToken 수명
- 15분~2시간으로 제한
- 중요 작업 시 추가 인증 요구
- 토큰 서명 보호
- 강력한 비밀키 사용(최소 256비트)
- 주기적인 키 교체(Key Rotation)
- 토큰 무효화 시스템: 토큰 무효화 시스템은 이미 발급된 JWT를 사용 불가능하게 만드는 방법
- Redis에 "blacklist:토큰값" 형태로 로그아웃된 토큰을 저장
- 토큰 검증 시 먼저 블랙리스트 확인 후 존재하면 거부
- 블랙리스트 항목은 토큰의 원래 만료 시간까지만 유지됨
- 보안 이슈 발생 시 특정 사용자의 모든 토큰 즉시 무효화 가능
- RefreshToken 순환: RefreshToken 순환은 토큰 갱신 과정에서 RefreshToken 자체도 새로 발급하는 방식
- 토큰 갱신 요청 시 기존 RefreshToken 검증 후 Redis에서 삭제
- 새 AccessToken과 함께 새 RefreshToken도 생성
- 새 RefreshToken을 Redis에 저장하고 쿠키로 클라이언트에 전송
- 이전 RefreshToken으로는 더 이상 갱신 불가능
- 탈취된 RefreshToken은 한 번만 사용 가능하므로 보안성 강화
- 이상 징후 감지
- IP 주소 변경 감지
- 동시 로그인 제한
- 다중 RefreshToken 사용 시도 감지
6. 결론
JWT 기반의 AccessToken과 RefreshToken을 함께 사용하는 인증 시스템은 보안과 사용자 경험 사이의 균형을 맞추는 효과적인 방법입니다. 스프링과 Redis를 활용하면 확장 가능하고 안전한 인증 시스템을 구축할 수 있습니다.
모든 보안 시스템이 그렇듯 완벽한 보안은 존재하지 않습니다. 지속적인 업데이트와 보안 모니터링을 통해 시스템을 개선해 나가는 것이 중요합니다. 특히 RefreshToken 관리에 주의를 기울이고, 토큰 탈취 가능성을 항상 염두에 두고 시스템을 설계해야 합니다.