세션 방식 (Session-Based Authentication)
간단한 동작 방식 정리
- 클라이언트가 로그인 요청을 하면 서버가 인증 후 세션 ID를 생성
- 세션 ID는 서버의 메모리에 저장
- 클라이언트는 이 세션 ID를 쿠키에 저장하여 매 요청 시 서버로 전송
- 서버는 요청마다 세션 ID를 확인하고 해당 세션의 정보를 조회하여 인증 처리
장점
- 구현이 간단하다
- 스프링 시큐리티를 사용하면 간단한 설정으로 세션 기반 인증, 인가 아키텍처를 사용 가능
- 서버와 클라이언트 간 구조가 직관적
- 서버에서 세션을 보관하기 때문에 쉽게 무효화하거나 관리할 수 있다
단점
- 서버 확장성
- 세션 정보를 서버에 저장하기 때문에 서버가 수평 확장되면 세션 동기화 문제가 있다.
- 스프링 시큐리티는 세션 정보를 공유할 수 있는 메커니즘을 제공한다
- 또는 레디스 같은 분산 캐시를 사용하여 여러 서버 간에 공유가 가능하다
- 세션 정보를 서버에 저장하기 때문에 서버가 수평 확장되면 세션 동기화 문제가 있다.
- 메모리 부하
- 사용자가 많아질수록 서버 메모리에 저장되는 세션 데이터가 많아져 성능 저하 가능성이 생긴다
취약점 및 공격 기법
- 세션 하이재킹(Session Hijacking), 세션 고정(Session Fixation)
- 공격자가 사용자의 세션 ID를 탈취하여 인증된 사용자로 가장하는 공격
- XSS: 악성 스크립트를 통해 쿠키에 저장된 세션 ID 탈취
- 패킷 스니핑: HTTP 환경에서 네트워크 패킷을 가로채 세션 ID 획득
- 세션 고정: 공격자가 미리 설정한 세션 ID를 사용자에게 사용하게 한 후 해당 세션 탈취
- 대응 방법
- HTTPS 사용 → 패킷 스니핑 방지
- 세션 ID 주기적으로 갱신 → 시큐리티는 세션 ID 변경 방식이 기본 설정
- HttpOnly (XSS 방어) 및 Secure(패킷 스니핑 방어)속성을 쿠키에 설정
- CSRF(Cross-Site Request Forgery)
- 사용자가 인증된 세션을 보유한 상태에서, 공격자가 악의적인 요청을 보내 피해를 유발하는 공격 사용자는 무의식적으로 자신의 권한을 공격에 제공한다(쿠키는 매 요청에 보내지니까)
- 대응방법
- CSRF 토큰 사용
- SameSite 쿠키 속성 설정 - strict, lax
JWT 토큰 방식 (Token-Based Authentication)
간단한 동작 방식 정리 (단일 토큰 기준)
- 클라이언트가 로그인 요청을 하면 서버가 인증 후 JWT를 생성하여 반환
- 클라이언트는 JWT를 로컬 스토리지나 세션 스토리지에 저장하고 Authorization 헤더에 담아 요청에 포함
- 서버는 JWT 서명을 검증하여 인증 처리
장점
- 확장성
- 서버가 상태를 유지할 필요가 없으므로(statelss) 수평 확장에 유리하고 마이크로서비스 구조에 적합
- Cross-Platform 용이성
- 모바일, 웹, API 등 다양한 클라이언트에서 쉽게 사용이 가능하다
- 빠른 검증
- JWT는 자체적으로 필요한 인증 정보를 담고 있어 별도의 데이터베이스 조회 없이 서명 검증만으로 빠른 인증이 가능하다
단점
- 토큰 무효화 어려움
- 발급된 JWT는 만료 시간 전까지 유효하다, 서버에서 임의로 토큰을 무효화하기 어렵다
- 보안 위험
- JWT가 탈취될 경우 유효 기간 동안 악용될 수 있다
취약점 및 공격 기법
- 토큰 탈취
- 공격자가 JWT를 탈취하여 유효 기간 동안 권한을 획득하는 공격
- XSS: 악성 스크립트를 통해 쿠키, 로컬 스토리지에 저장된 토큰 탈취
- 중간자 공격: HTTPS가 아닌 환경에서 네트워크 가로채기
- 대응 방법
- HTTPS 적용
- JWT를 로컬 스토리지가 아닌 HttpOnly 쿠키에 저장 → XSS 방어
- CSRF
- 세션과 동일
나의 선택은?
나는 이번 프로젝트를 JWT 방식으로 진행하려고 한다. 그 이유와 JWT 단점 해결 방식을 아래에 정리해 봤다
현대 애플리케이션의 표준: JWT 방식
- 현대 애플리케이션 아키텍처 대부분의 모바일 앱, 웹, 마이크로서비스 아키텍처에서는 JWT가 표준으로 자리 잡았다.
- OAuth 기반 소셜 로그인과의 유사성 비록 현재 프로젝트에서는 OAuth를 직접 사용하지 않지만, OAuth2.0 프로토콜 역시 JWT 기반의 액세스 토큰을 활용하여 인증과 권한 부여를 처리한다.
서버 확장성(Scalability) 측면의 이점
- 세션 방식의 한계 세션 기반은 서버 메모리 또는 중앙 저장소(DB)에 상태를 유지해야 하므로, 세션 공유에 대한 추가적인 조치가 필요하다.
- JWT의 강점 JWT는 자체적으로 사용자 정보와 인증 상태를 포함하고 있어 별도의 세션 저장소가 필요 없다.서버 간 로드 밸런싱 최적화: 어떤 서버로 요청이 분산되더라도 토큰만 있으면 인증 가능
- 수평 확장(Scale-Out) 용이: 여러 서버 인스턴스 간 세션 동기화 이슈 없이 자유롭게 확장 가능
토큰 탈취 대응: Refresh Token을 통한 이중화 전략
- 문제점 JWT는 한 번 탈취되면 만료 시점까지 유효하여 Replay Attack 위험 존재.
- 해결 전략
- Access Token (단기 토큰): 유효 기간을 짧게 설정
- Refresh Token (장기 토큰): 만료된 Access Token 재발급에 사용
- 이점
- 토큰 탈취 피해 최소화: Access Token 탈취 시에도 짧은 만료 시간으로 피해 범위 제한
- 추가 인증 절차: Refresh Token 사용 시 별도의 검증 로직을 추가하여 보안 강화
JWT 무효화 방식 도입
- 보통 로그아웃 시 만료되지 않은 Access Token을 Redis 블랙리스트에 등록하여 해당 토큰을 무효화
- 내가 하려는 프로젝트는 여러 미디어에 동시에 세션을 유지하는 방식이 아닌 마치 게임에 접속하듯이 하나의 세션을 유지하려고 한다 그래서 블랙리스트 방식이 필요하지 않다고 생각한다.
- 대안 전략
- Refresh Token 자체를 Redis에서 삭제
- 공격자가 탈취한 Refresh Token으로 재발급 요청 시, Redis에 존재하지 않으므로 즉시 거절
- 장점
- 블랙리스트 조회 과정이 불필요, 단순한 Redis 키 존재 여부 확인만으로 처리 → 더 빠른 검증
- 불필요한 무효화 토큰 저장이 없으므로 Redis 메모리 자원 절약
- Refresh Token 자체를 관리하므로, 탈취된 토큰의 재사용 가능성 완전 차단
- Access Token은 짧은 만료 시간을 설정하여 방치하고, Refresh Token 중심으로 관리
- 단점 및 보안점
- Access Token은 만료 전까지 여전히 유효 → 만료 시간 짧게 설정
- Redis 장애 발생시 전체 서버의 재발급 로직에 영향 → Redis 이중화
Access/Refresh 토큰 저장 위치
클라이언트에서 발급 받은 JWT를 저장하기 위해 로컬 스토리지와 쿠키에 대해 많은 고려를 한다. 각 스토리지에 따른 특징과 취약점은 아래와 같다.
- 로컬 스토리지 : XSS 공격에 취약함 : Access 토큰 저장
- httpOnly 쿠키 : CSRF 공격에 취약함 : Refresh 토큰 저장
고려
Refresh 토큰은 주로 쿠키에 저장된다. 쿠키는 XSS 공격을 받을 수 있지만 httpOnly를 설정하면 완벽히 방어할 수 있다. 그럼 가장 중요한 CSRF 공격에 대해 위험하지 않을까라는 의구심이 생긴다.
하지만 Refresh 토큰의 사용처는 단 하나인 토큰 재발급 경로이다. CSRF는 Access 토큰이 접근하는 회원 정보 수정, 게시글 CRUD에 취약하지만 토큰 재발급 경로에서는 크게 피해를 입힐 만한 로직이 없기 때문이다.