JWT 리프레시 토큰의 보안 저장을 위한 쿠키 설정과 보안 프로토콜 연동

보안이 뚫린 리프레시 토큰이 데이터 유출로 이어지는 과정을 보여주며, 토큰을 안전한 금고에 저장하는 것이 왜 중요한지 강조하는 개념 설명 이미지입니다.

JWT 리프레시 토큰의 보안 취약성과 저장의 중요성

JWT(JSON Web Token) 기반 인증 시스템에서 액세스 토큰(Access Token)은 주로 클라이언트 측 자바스크립트가 접근할 수 있어야 하므로 로컬 스토리지(LocalStorage)나 세션 스토리지(SessionStorage)에 저장하는 것이 일반적입니다. 그러나 수명이 길고, 새로운 액세스 토큰을 발급받는 열쇠 역할을 하는 리프레시 토큰(Refresh Token)의 경우, 이와 동일한 방식으로 저장하는 것은 심각한 보안 위험을 초래합니다. XSS(Cross-Site Scripting) 공격에 취약한 클라이언트 스토리지에 장기간 유효한 자격 증명을 보관하는 것은, 공격자가 토큰을 탈취하여 사용자의 세션을 무기한 유지할 수 있게 만드는 것과 같습니다. 결과적으로 리프레시 토큰의 저장소 선택은 단순한 구현 편의성의 문제가 아닌, 시스템 전체의 보안 수준을 결정하는 핵심 설계 요소입니다.

보안 강화를 위한 쿠키 저장 전략: HttpOnly & Secure & SameSite

리프레시 토큰을 XSS 공격으로부터 보호하는 가장 효과적인 방법은 클라이언트 측 자바스크립트가 읽을 수 없는 공간에 저장하는 것입니다. 이 요구사항을 완벽히 충족하는 것이 바로 HttpOnly, Secure, SameSite 속성이 설정된 HTTP 쿠키입니다. 이 세 가지 속성은 현대 웹 보안의 필수 요건으로, 각각 다음과 같은 방어 계층을 구성합니다.

보안이 뚫린 리프레시 토큰이 데이터 유출로 이어지는 과정을 보여주며, 토큰을 안전한 금고에 저장하는 것이 왜 중요한지 강조하는 개념 설명 이미지입니다.

HttpOnly: XSS 공격에 대한 1차 방어선

HttpOnly 플래그가 설정된 쿠키는 브라우저에 의해 자동으로 관리되며, JavaScript의 document.cookie API를 통해 접근하거나 읽는 것이 완전히 차단됩니다. 이는 악성 스크립트가 주입되더라도 쿠키에 저장된 리프레시 토큰을 직접 탈취할 수 없음을 의미합니다. 리프레시 토큰은 오직 서버로 전송되는 HTTP 요청에 의해 자동으로 첨부될 뿐입니다. 따라서 토큰 갱신 요청은 반드시 서버 측 API 엔드포인트를 통해 이루어져야 하며, 클라이언트 로직은 이 과정에 간섭할 수 없습니다.

Secure & SameSite: 전송 채널 및 출처 검증

Secure 속성은 쿠키가 오직 HTTPS 프로토콜을 통한 암호화된 연결에서만 서버로 전송되도록 강제합니다. 이처럼 hTTP 같은 비암호화 채널에서는 전송이 차단되어 중간자 공격(MITM)에 의한 탈취 위험을 근본적으로 차단합니다. SameSite 속성은 CSRF(Cross-Site Request Forgery) 공격을 방지하는 데 핵심적입니다. SameSite=Strict 또는 SameSite=Lax로 설정하면, 쿠키가 동일 사이트(Same-Site)에서 발생한 요청에만 첨부되도록 제한합니다. 타사 사이트에서 발생한 교차 사이트 요청에는 쿠키가 포함되지 않아, 악의적인 사이트가 사용자의 인증된 상태를 이용해 백그라운드에서 요청을 보내는 것을 방지합니다.

실전 구현: 서버 측 쿠키 설정 코드 예시 및 프로토콜

이론을 바탕으로 Node.js(Express)와 Spring Boot 환경에서 리프레시 토큰을 안전하게 설정하는 실제 코드를 비교 분석하겠습니다. 핵심은 토큰 발급 시점에 응답 헤더에 올바른 속성을 가진 쿠키를 심는 것입니다.

Node.js (Express) 구현 예시

Express 프레임워크에서는 res.cookie()</code 메서드를 사용하여 쿠키를 설정합니다. 리프레시 토큰 발급 또는 갱신이 이루어지는 로그인 또는 /refresh-token 엔드포인트에서 다음과 같이 응답해야 합니다.

res.cookie('refresh_token', newRefreshToken, {
 httpOnly: true,
 secure: true, // 프로덕션 환경에서는 반드시 true
 sameSite: 'strict', // 또는 'lax'. CSRF 보호 수준 조정
 maxAge: 7 * 24 * 60 * 60 * 1000, // 7일 (밀리초 단위)
 path: '/api/auth/refresh' // 토큰 갱신 요청을 받는 특정 경로로 제한
});
// 액세스 토큰은 JSON 본문으로 클라이언트에 반환
res.json({ accessToken: newAccessToken });

이 설정의 핵심은 path 옵션입니다. 리프레시 토큰 쿠키를 특정 경로(예: /api/auth/refresh)로 제한하면, 해당 경로로의 요청에만 쿠키가 자동으로 첨부됩니다. 사이트 내的其他 모든 API 요청에는 이 쿠키가 포함되지 않아, 노출 면적을 최소화할 수 있습니다.

Spring Boot 구현 예시

Spring Boot에서는 HttpServletResponse 객체를 이용하거나, ResponseCookie 빌더를 사용하는 방법이 있습니다. 더 명시적인 ResponseCookie 방식을 추천합니다.

ResponseCookie refreshTokenCookie = ResponseCookie
 .from("refresh_token", newRefreshToken)
 .httpOnly(true)
 .secure(true) // 프로덕션 환경 true
 .sameSite("Strict") // Spring Boot 2.7+, Spring Security 5.7+
 .path("/api/auth/refresh")
 .maxAge(Duration.ofDays(7))
 .build();
response.addHeader(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString());
// 응답 본문에 액세스 토큰 반환

보안 프로토콜 연동: CORS 및 CSRF 보호와의 협력

안전한 쿠키 설정만으로는 완벽한 보안을 기대할 수 없으며, 웹 애플리케이션의 다른 보안 메커니즘과 유기적으로 연동되어 시너지를 창출해야 합니다. 특히 SameSite 쿠키 정책과 CORS(Cross-Origin Resource Sharing), 그리고 CSRF 토큰 간의 상호작용 체계를 정교하게 설계하는 과정이 핵심적입니다.

네트워크 보안 로직이 가동되는 스모크오일솔트 운영 환경 내의 통신 아키텍처상에서는 브라우저가 요청을 전송할 때 쿠키의 전송 범위를 결정하는 정책과 서버 측의 출처 허용 목록이 밀밀하게 결합되어 작동합니다.

이러한 다층 방어 구조는 허가되지 않은 제3자 사이트에서 발생하는 악의적인 요청을 원천적으로 차단하고, 정상적인 API 호출에 대해서만 자원 접근을 허용하는 기술적 기반이 됩니다. 결과적으로 각 보안 프로토콜 간의 세밀한 연동은 세션 하이재킹이나 교차 사이트 요청 위조와 같은 공격으로부터 시스템의 신뢰성을 보호하며 안전한 사용자 인증 상태를 유지하는 중추적인 역할을 수행합니다.

CORS 정책과의 조화

프론트엔드(예: React 앱, 도메인: app.example.com)와 백엔드 API(도메인: api.example.com)가 다른 도메인에 호스팅되는 경우, CORS 정책이 필수적입니다. 백엔드 서버는 프론트엔드 출처를 허용 목록에 추가하고, 인증 정보(쿠키)를 포함한 요청을 허용하도록 설정해야 합니다.

// Express CORS 설정 예시
const corsOptions = {
 origin: 'https://app.example.com',
 credentials: true // Access-Control-Allow-Credentials: true 설정
};
app.use(cors(corsOptions));

credentials: true 설정이 없으면, 브라우저는 교차 출처 요청에 쿠키를 첨부하지 않으며, 서버의 응답 쿠키도 프론트엔드에서 받아들여지지 않습니다. 이 설정은 SameSite=None이 아니더라도, 같은 사이트 내 서브도메인 간(api.example.com -> app.example.com) 또는 명시적으로 허용된 출처 간 쿠키 전송을 가능하게 합니다.

CSRF 보호의 역할 변화

SameSite=Strict 또는 Lax 쿠키는 대부분의 CSRF 공격 시나리오를 효과적으로 차단합니다. 특히 상태 변경 요청(POST, PUT, DELETE)에 대해 Strict를 사용하면 완벽에 가까운 방어가 가능합니다. 웹 애플리케이션 보안 취약점 대응 체계를 연구하는 한국인터넷진흥원 (KISA)의 최신 보안 가이드라인에 따르면, 현대적인 브라우저 환경에서는 SameSite 쿠키 설정을 기본 방어 수단으로 삼되 보안 요구사항이 극도로 높은 특정 엔드포인트에 한해 CSRF 토큰을 병행하는 전략이 권고되고 있습니다. 그러나 일부 레거시 환경이나 특수한 통합 시나리오에서는 추가 보안층으로 CSRF 토큰을 여전히 사용할 수 있습니다. 리프레시 토큰 갱신 요청 자체는 SameSite 쿠키와 HttpOnly 속성의 결합만으로도 충분한 보호 수준을 유지할 수 있으며, 이는 개발 편의성과 보안 강도를 동시에 확보할 수 있는 효율적인 구성안이 됩니다.

대안 저장소 비교 분석: 쿠키 vs 웹 스토리지 vs 메모리

리프레시 토큰 저장을 위한 주요 방법들의 보안성, 편의성, 지속성을 종합적으로 비교하면 다음과 같습니다. 이 비교는 보안 위험 관리 차원에서 반드시 필요한 분석입니다.

저장소 XSS 공격 대응 CSRF 공격 대응 지속성 접근성 주요 위험
HttpOnly/Secure/SameSite 쿠키 탁월 (JavaScript 접근 불가) 우수 (SameSite 속성에 따라) 만료일까지 지속 자동으로 해당 도메인/경로 요청에 첨부 교차 출처 설정 오류, 구형 브라우저 호환성
로컬/세션 스토리지 취약 (JavaScript로 직접 접근 가능) 해당사항 없음 (수동 첨부 필요) 로컬 스토리지: 영구적 / 세션 스토리지: 탭 종료 시 전역 JavaScript 접근 가능 XSS 공격 시 토큰 직접 탈취 가능성이 매우 높음
클라이언트 메모리 (변수) 우수 (페이지 새로고침 시 소멸) 해당사항 없음 매우 낮음 (페이지/탭 종료 시 소멸) 애플리케이션 코드 내 접근 가능 사용자 경험 저하 (자주 재로그인 필요), SPA 탭 간 공유 불가

위 표의 데이터 분석에 기반할 때, 보안성과 사용자 경험의 균형을 맞추는 최적의 전략은 다음과 같이 요약됩니다.

  • 리프레시 토큰: HttpOnly, Secure, SameSite=Strict/Lax 쿠키에 저장. 이는 XSS로부터의 탈취 위험을 약 99% 이상 감소시키는 가장 효과적인 방법입니다.
  • 액세스 토큰: 클라이언트 메모리(예: React state, Vue ref) 또는 세션 스토리지에 저장. 짧은 수명(예: 15분)을 가지므로, XSS 공격에 노출되더라도 피해 기간이 제한적입니다.
  • 보안 강화 패키지: 프론트엔드 빌드 시 CSP(Content Security Policy) 헤더를 구성하여 XSS 발생 가능성을 사전에 차단하는 것이 근본적인 해결책입니다.

리스크 관리 및 주의사항: 구현 시 발생할 수 있는 사고 유형

설계가 완벽하더라도 구현 단계의 실수나 환경 차이는 심각한 보안 결함으로 이어질 수 있습니다. 다음은 실제 운영에서 빈번히 발생하는 문제와 그 예방책입니다.

주의사항 1: 개발/프로덕션 환경 차이
Secure 속성은 HTTPS에서만 동작합니다, 로컬 개발 환경(http)에서는 이 속성을 false로 일시적으로 설정해야 할 수 있으나, 절대 프로덕션 코드에 secure: false가 포함되어 배포되지 않도록 환경 변수(예: node_env)를 통해 조건부로 제어해야 합니다. 한 번의 배포 실수가 모든 사용자의 토큰을 노출시킬 수 있습니다.

주의사항 2: SameSite 정책과 구형 브라우저
SameSite=None을 사용하려면 반드시 Secure 속성이 함께 설정되어야 합니다. 그렇지 않으면 최신 브라우저가 쿠키를 거부합니다. 게다가 Internet Explorer 등 일부 구형 브라우저는 SameSite 속성을 지원하지 않아 예상치 못한 동작을 보일 수 있습니다. 지원 브라우저 목표에 따라 SameSite 정책을 선택하고, 폴백(fallback) 전략을 고려해야 합니다.

주의사항 3: 토큰 갱신 로직의 무한 루프 및 교착 상태
리프레시 토큰 역시 만료되거나, 사용자가 비밀번호를 변경하거나, 관리자가 강제 로그아웃시킬 수 있어야 합니다(토큰 블랙리스트 또는 무효화 메커니즘). 또한 액세스 토큰 갱신 요청 중에 리프레시 토큰까지 만료된 경우, 또는 동시에 여러 탭에서 갱신 요청이 발생하는 경우를 처리하지 않으면 사용자가 갑자기 로그아웃되거나 요청이 실패할 수 있습니다. 이를 방지하려면 서버 측에서 토큰 재사용 감지 및 동시 요청 제어 로직을 구현해야 합니다.

주의사항 4: 쿠키 경로(Path) 제한의 부작용
리프레시 토큰 쿠키의 경로를 /api/auth/refresh로 제한하는 것은 보안상 유리하지만, 프론트엔드 애플리케이션이 해당 경로 이외의 다른 방법(예: 특정 헤더를 인식)으로 갱신 요청을 보내야 함을 의미합니다. 이는 표준화된 방식이 아니며, 커스텀 구현이 필요합니다. 표준 OAuth 2.0 방식을 따르는 것이 장기적인 유지보수와 호환성 측면에서 더 유리할 수 있습니다.

마무리하면, JWT 리프레시 토큰을 HttpOnly, Secure, SameSite 쿠키에 저장하는 방식은 XSS와 CSRF 공격에 대한 현실적이고 효과적인 방어책입니다. 그러나 이는 단일 기술이 아닌, CORS 정책, 환경별 설정 관리, 강력한 토큰 무효화 전략, 그리고 궁극적으로 XSS 취약점을 차단하는 CSP와 같은 다층 보안(Multi-layered Security) 체계의 한 부분으로 통합되어야 그 진정한 효과를 발휘합니다.

이러한 다층 보안의 핵심은 단순히 토큰을 잘 숨기는 것에 그치지 않고, 시스템 전반의 ‘암호화 신뢰 체인’을 어떻게 관리하느냐에 있습니다. 예를 들어, KMS를 활용한 암호화 키 관리 전략과 데이터 보안 계층의 구조적 분석 사례처럼 토큰 서명에 사용되는 비밀키나 데이터베이스에 저장된 민감 정보를 하드웨어 보안 모듈(HSM) 기반의 키 관리 서비스(KMS)로 보호한다면, 애플리케이션 서버가 침해당하더라도 핵심 권한이 유출되는 최악의 시나리오를 방지할 수 있습니다.

결국 구현 시 위에 명시된 주의사항을 세심히 검토하고, 보안 테스트를 반드시 거치는 것이 시스템 신뢰성을 확보하는 최선의 경로입니다. 강력한 쿠키 설정과 정교한 키 관리 전략이 결합될 때, 비로소 외부 공격으로부터 사용자의 세션과 데이터를 안전하게 보호하는 견고한 데이터 보안 계층이 완성됩니다.

Contact Us

자율주행의 미래를 함께 만들어갑니다

최신 자율주행 전기차 및 모빌리티 트렌드를 확인하고, 미래 모빌리티의 혁신적인 변화를 경험하세요.

모든 기사 보기 →