티스토리 뷰

반응형

안녕하세요! 온라인 서비스의 보안에 관심 있는 모든 분들을 위해, 오늘날 디지털 세상에서 없어서는 안 될 중요한 보안 개념인 Access TokenRefresh Token에 대해 깊이 있게 다뤄보려 합니다. 웹사이트 로그인부터 모바일 앱 서비스 이용, 다양한 API를 통한 데이터 교환까지, 현대 사회에서 사용자 인증은 그 어느 때보다 중요해졌습니다. 하지만 기존의 인증 방식에는 여러 한계점이 존재했고, 이를 극복하기 위해 토큰 기반 인증이라는 새로운 패러다임이 등장했습니다.

이 글에서는 기본적인 사용자 인증 방식의 문제점에서 출발하여, Access Token이란 무엇인지, Refresh Token이란 무엇인지 그 정의와 역할, Access Token Refresh Token 차이점, 그리고 이 두 토큰이 Access Token Refresh Token 동작 방식을 통해 어떻게 상호작용하며 안전한 시스템을 구축하는지 심층적으로 분석할 것입니다. 나아가 Access Token Refresh Token 보안을 위한 필수적인 고려사항과 OAuth 2.0 Access Token Refresh Token의 실제 활용 사례까지, 비전공 일반인부터 개발자, 시스템 관리자에 이르기까지 모든 독자들이 명확한 이해를 얻을 수 있도록 쉽고 전문적인 설명을 병행할 예정입니다. 안전하고 효율적인 사용자 인증 시스템의 비밀을 함께 파헤쳐 볼 준비가 되셨나요?

 


안전한 사용자 인증의 진화: 토큰 기반 인증이 필요한 이유

오늘날 우리가 사용하는 대부분의 온라인 서비스는 사용자가 '나'라는 것을 증명해야만 특정 기능이나 정보에 접근할 수 있도록 설계되어 있습니다. 이것이 바로 사용자 인증 방식의 핵심이죠. 과거에는 주로 세션(Session) 기반의 인증 방식이 사용되었습니다. 사용자가 아이디와 비밀번호로 로그인하면, 서버는 사용자 정보를 기억하기 위한 고유한 세션 ID를 발급하고 이를 클라이언트(웹 브라우저 등)의 쿠키(Cookie)에 저장하는 방식이었습니다. 이후 클라이언트는 요청마다 이 세션 ID를 서버로 보내 자신이 로그인한 사용자임을 증명했습니다.

하지만 이러한 세션 기반 방식에는 몇 가지 치명적인 한계와 보안 취약점이 존재했습니다.

1. 세션 기반 인증의 주요 문제점

  • 확장성(Scalability) 문제: 사용자가 늘어나 서버를 여러 대로 확장해야 할 경우, 어떤 서버에 요청하더라도 동일한 세션 정보를 공유해야 하는 '세션 동기화' 문제가 발생합니다. 이는 시스템 복잡도를 높이고, 서버 확장을 어렵게 만드는 요인이 됩니다.
  • 보안 문제: 세션 ID를 담은 쿠키가 탈취당하면 공격자는 해당 사용자인 척 시스템에 접근할 수 있게 됩니다 (세션 하이재킹). 또한, 크로스 사이트 요청 위조(CSRF)와 같은 공격에 취약해 사용자가 의도하지 않은 요청을 보내게 만들 수도 있습니다. 한 번 탈취당한 세션은 사용자가 로그아웃하거나 세션 만료 시간까지 유효하다는 점도 큰 문제입니다.
  • 모바일 환경 및 마이크로서비스 아키텍처의 한계: 세션은 주로 웹 브라우저 기반의 환경에 최적화되어 있었지만, 모바일 앱, 다양한 클라이언트, 그리고 독립적인 작은 서비스들로 구성된 마이크로서비스 아키텍처에서는 세션을 공유하고 관리하는 것이 매우 번거로워졌습니다.

2. 토큰 기반 인증의 등장

이러한 문제점들을 해결하기 위해 등장한 것이 바로 토큰 기반 인증 방식입니다. 토큰 기반 인증은 서버가 사용자 상태를 저장하지 않는 '무상태(Stateless)' 방식으로, 각 요청에 필요한 모든 정보를 토큰 내에 담아 보내는 것이 특징입니다. 이 방식은 서버의 확장성을 크게 향상시키고, 다양한 클라이언트 환경에서도 유연하게 적용될 수 있습니다.

토큰 기반 인증의 주요 장점:

  • 무상태성(Stateless): 서버가 클라이언트의 상태를 저장하지 않아 서버 확장이 용이합니다.
  • 확장성(Scalability): 여러 서버에서 동일한 토큰을 검증할 수 있습니다.
  • 다양한 클라이언트 지원: 웹, 모바일 앱, 데스크톱 등 어떤 클라이언트에서도 동일한 인증 메커니즘을 사용할 수 있습니다.
  • 새로운 보안 취약점 대응: 기존 세션 방식의 취약점에 대한 새로운 대응 방식을 제공합니다.

그리고 이 토큰 기반 인증의 핵심에 Access TokenRefresh Token이라는 두 개의 토큰이 존재하며, 이들은 상호 보완적인 역할을 수행하며 보안과 편의성이라는 두 마리 토끼를 잡으려 합니다.


Access Token 심층 분석: 역할, JWT 구조, 그리고 한계

Access Token토큰 기반 인증 시스템에서 가장 핵심적인 요소 중 하나입니다. 직역하면 '접근 토큰'으로, 특정 리소스에 접근할 수 있는 권한을 부여하는 '단기 권한 증명서'와 같다고 비유할 수 있습니다. 사용자가 로그인에 성공하면 서버는 이 Access Token을 발급하고, 클라이언트는 이 토큰을 매번 API 요청의 헤더(Authorization 헤더)에 담아 서버로 전송합니다. 서버는 이 토큰을 검증하여 해당 요청을 보낸 사용자가 유효한 권한을 가지고 있는지 확인하고, 그에 따라 요청을 처리합니다.

1. Access Token이란? 정의와 핵심 역할

Access Token이란 사용자가 인증을 완료한 후, 서버로부터 발급받아 일정 기간 동안 특정 리소스(API, 데이터 등)에 직접 접근할 수 있는 권한을 증명하는 암호화된 문자열입니다. 이 토큰은 일반적으로 사용자 ID, 권한(예: '읽기', '쓰기'), 토큰 발급 시간, 만료 시간 등 필요한 정보들을 포함하고 있습니다. 서버는 이 토큰만으로 사용자의 신원과 권한을 파악하여 요청을 처리하기 때문에, 매번 데이터베이스를 조회할 필요가 없어 성능 향상에도 기여합니다.

핵심 역할:

  • 리소스 접근 권한 증명: 보호된 리소스(API 엔드포인트)에 접근할 수 있는 단기적인 권한을 부여합니다.
  • 무상태성 유지: 서버가 클라이언트의 상태를 기억할 필요 없이 토큰 자체로 인증 및 권한을 판단합니다.
  • 성능 향상: 매번 데이터베이스 조회 없이 토큰 검증만으로 요청을 처리할 수 있습니다.

2. JWT (JSON Web Token) 구조: Access Token의 표준

Access Token은 주로 JWT(JSON Web Token) 표준을 따릅니다. JWT는 정보를 안전하게 전송하기 위한 간결하고 자체 포함(self-contained)적인 방식입니다. JWT는 세 부분으로 구성됩니다:

  • Header (헤더): 토큰의 타입(JWT)과 서명에 사용된 알고리즘(예: HMAC SHA256 또는 RSA)을 정의합니다. Base64Url로 인코딩됩니다.
  • { "alg": "HS256", "typ": "JWT" }
  • Payload (페이로드): 실제 정보를 담는 부분입니다. 이 정보들을 클레임(Claim)이라고 부르며, 사용자 ID, 권한, 토큰 만료 시간(exp), 발급 주체(iss) 등 다양한 정보를 포함할 수 있습니다. Base64Url로 인코딩됩니다. 민감한 정보는 담지 않는 것이 중요합니다.
  • { "sub": "1234567890", "name": "John Doe", "iat": 1516239022, "exp": 1516239082, // 만료 시간 (Unix Timestamp) "roles": ["admin", "user"] }
  • Signature (서명): 헤더와 페이로드를 Base64Url로 인코딩한 값을 서버의 비밀 키(Secret Key)로 암호화하여 생성합니다. 이 서명은 토큰이 변조되지 않았음을 검증하는 데 사용됩니다. 서명이 유효하지 않으면 서버는 해당 토큰을 거부합니다.

세 부분을 .으로 연결하면 최종 Access Token 문자열이 됩니다.
예시: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwODIsInJvbGVzIjpbImFkbWluIiwidXNlciJdfQ.S_U-W0Xf0Z0Q9R7p_...

import base64
import json

def decode_jwt_part(token_part):
    # JWT는 Base64Url 인코딩을 사용하므로, 패딩을 추가해야 할 수 있습니다.
    # Base64Url은 - 대신 +, _ 대신 / 를 사용하고, 패딩(=)을 생략합니다.
    padded_part = token_part + '=' * (-len(token_part) % 4)
    decoded_bytes = base64.urlsafe_b64decode(padded_part)
    return json.loads(decoded_bytes.decode('utf-8'))

# 예시 JWT (서명 부분은 생략)
jwt_example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyMzkwODIsInJvbGVzIjpbImFkbWluIiwidXNlciJdfQ.SIGNATURE_PART_HERE"
parts = jwt_example.split('.')

if len(parts) == 3:
    header = decode_jwt_part(parts[0])
    payload = decode_jwt_part(parts[1])

    print("--- JWT Header ---")
    print(json.dumps(header, indent=2))
    print("\n--- JWT Payload ---")
    print(json.dumps(payload, indent=2))
else:
    print("유효하지 않은 JWT 형식입니다.")

3. Access Token의 짧은 유효 기간: 이유와 한계

Access Token은 의도적으로 짧은 유효 기간을 가집니다. 이는 보안 취약점을 최소화하기 위한 중요한 전략입니다.

장점:

  • 보안 강화 (단기 유효): 토큰 탈취 시 공격자가 토큰을 악용할 수 있는 시간이 제한되므로, 피해를 최소화할 수 있습니다.
  • 무상태성 및 확장성: 서버가 클라이언트 상태를 저장할 필요 없이 토큰 자체로 검증 가능하여, 서버 확장이 용이하고 마이크로서비스 환경에 적합합니다.
  • 다양한 클라이언트 지원: 웹, 모바일, 데스크톱 등 어떤 클라이언트에서도 사용 가능합니다.

단점:

  • 잦은 재발급 필요: 짧은 유효 기간 때문에 사용자는 Access Token이 만료될 때마다 Access Token 재발급 과정을 거쳐야 합니다. 이는 사용자 경험을 저해하거나 추가적인 개발 복잡성을 야기할 수 있습니다.
  • 탈취 시 위험 (유효 기간 내): 비록 짧은 기간이라 할지라도, 유효한 Access Token이 탈취되면 공격자는 그 기간 동안 사용자 권한으로 서비스에 접근할 수 있습니다.
  • 만료 시 불편: 토큰이 만료되면 API 요청이 실패하고, 재인증 과정이 필요합니다.

이러한 Access Token의 한계, 특히 짧은 유효 기간으로 인한 불편함은 Refresh Token의 등장 배경이 됩니다. Refresh Token은 이 단점을 보완하며 Access Token과 함께 안전하고 편리한 인증 시스템을 구축하는 데 필수적인 역할을 합니다.


Refresh Token 완벽 가이드: Access Token 만료를 극복하는 핵심 전략

앞서 살펴보았듯이, Access Token짧은 유효 기간 덕분에 보안 취약점 발생 시 피해를 최소화할 수 있다는 장점이 있습니다. 하지만 동시에 그 짧은 유효 기간 때문에 사용자는 일정 시간마다 로그인을 다시 하거나, 새로운 Access Token을 발급받아야 하는 번거로움에 직면하게 됩니다. 이러한 불편함을 해소하고 사용자 경험을 저해하지 않으면서도 보안을 유지하기 위해 등장한 것이 바로 Refresh Token입니다. Refresh TokenAccess Token 재발급을 위한 '영리한 전략'이자 '갱신 가능한 회원권'과 같은 역할을 수행합니다.

1. Refresh Token이란? 정의와 핵심 역할

Refresh Token이란 Access Token이 만료되었을 때, 사용자에게 재로그인 없이 새로운 Access Token을 발급받을 수 있도록 해주는 특수한 토큰입니다. 이 토큰은 Access Token보다 훨씬 장기 유효 기간을 가지며, 주로 사용 시마다 새로운 Refresh Token을 발급하는 토큰 회전(Token Rotation) 방식을 채택하거나, 한 번 사용하면 무효화되는 일회성 토큰으로 설계되기도 합니다.

Refresh Token의 핵심 역할:

  • 사용자 편의성 증대: Access Token짧은 유효 기간으로 인한 잦은 재로그인 없이 장기간 인증 상태를 유지합니다.
  • 보안 강화 지원: Access Token짧은 유효 기간을 유지할 수 있게 하여, Access Token 탈취 시 피해 범위를 줄입니다. 만약 Access Token만으로 장기 세션을 유지한다면, 탈취 시 피해가 매우 커질 것입니다.

2. 장기 유효 기간의 이유와 Access Token 재발급 과정

Refresh Token장기 유효 기간을 가지는 이유는 사용자가 오랜 시간 동안 서비스에 로그인 상태를 유지할 수 있도록 하기 위함입니다. 일반적으로 Refresh Token은 몇 주, 몇 달, 심지어 영구적으로 유효하도록 설정되기도 합니다 (물론 보안을 위해 적절한 만료 시간을 설정하는 것이 권장됩니다).

Access Token 재발급 과정:

  1. Access Token 만료: 클라이언트가 Access Token을 사용하여 API 요청을 보냈지만, 서버에서 토큰이 만료되었음을 확인하고 401 Unauthorized 에러를 반환합니다.
  2. Refresh Token 사용 요청: 클라이언트는 서버의 인증 엔드포인트(endpoint)로 Refresh Token을 보내 새로운 Access Token 발급을 요청합니다.
  3. Refresh Token 검증 및 재발급: 서버는 Refresh Token의 유효성을 검사합니다. 이 과정에서 Refresh Token이 유효하고, 탈취되지 않았는지 확인하며, 데이터베이스에 저장된 Refresh Token과 비교하는 등의 검증 절차를 거칩니다.
  4. 새로운 Access Token 발급: Refresh Token이 유효하다고 판단되면, 서버는 새로운 Access Token을 (그리고 선택적으로 새로운 Refresh Token도) 발급하여 클라이언트에게 전송합니다.
  5. API 요청 재시도: 클라이언트는 새로 발급받은 Access Token으로 원래의 API 요청을 다시 시도합니다.

이 과정에서 중요한 것은 Refresh Token의 유효성 검증과 서버 측에서의 안전한 관리가 필수적이라는 점입니다.

3. Refresh Token 저장 및 관리의 중요성

Refresh Token장기 유효 기간을 가지기 때문에 Access Token보다 훨씬 더 보안 취약점의 중심이 될 수 있습니다. 만약 Refresh Token 탈취가 발생하면, 공격자는 이를 이용해 지속적으로 새로운 Access Token을 발급받아 시스템에 접근할 수 있게 됩니다. 이는 사용자 계정에 대한 영구적인 접근 권한을 탈취하는 것과 같으므로, Refresh Token의 저장 및 관리는 Access Token Refresh Token 보안에 있어 가장 중요한 부분 중 하나입니다.

주요 관리 전략:

  • HttpOnly Cookie: Refresh Token은 클라이언트 측에서 JavaScript로 접근할 수 없는 HttpOnly 속성을 가진 쿠키에 저장하는 것이 일반적인 권장 사항입니다. 이는 XSS(Cross-Site Scripting) 공격으로부터 Refresh Token 탈취를 방지하는 데 효과적입니다.
  • Secure 속성: HTTPS를 통해서만 전송되도록 Secure 속성을 설정해야 합니다.
  • SameSite 속성: CSRF 공격을 방지하기 위해 SameSite=Lax 또는 SameSite=Strict 속성을 설정하여, 동일한 도메인에서 시작된 요청에만 쿠키가 전송되도록 제한합니다.
  • 서버 측 저장 및 관리: Refresh Token은 서버 측 데이터베이스에 저장하여 유효성을 검증하고, 필요시 언제든지 강제로 만료시킬 수 있도록 관리해야 합니다.
  • 토큰 회전(Rotation): Refresh Token을 한 번 사용하면 즉시 폐기하고, 새로운 Refresh Token을 발급하는 토큰 회전 전략을 사용하면 Refresh Token이 탈취되었을 때 공격자가 사용할 수 있는 기회를 줄일 수 있습니다.
  • 비정상 접근 탐지: IP 주소 또는 사용자 에이전트(User-Agent) 변경 등 Refresh Token 요청 시 비정상적인 접근을 탐지하고 차단합니다.

4. Refresh Token의 장단점

장점:

  • 사용자 경험 개선: Access Token짧은 유효 기간으로 인한 잦은 재로그인 없이 장기간 인증 상태를 유지할 수 있습니다.
  • 보안 강화 (Access Token): Access Token이 탈취되더라도 짧은 유효 기간 덕분에 피해를 제한하고, Refresh Token을 통해 새로운 Access Token을 발급받을 수 있으므로 전체적인 Access Token Refresh Token 보안이 향상됩니다.
  • 공격 표면 축소: Refresh TokenAccess Token보다 사용 빈도가 훨씬 낮으므로, 네트워크를 통한 Refresh Token 탈취 기회가 적습니다.

단점:

  • 높은 보안 위험 (탈취 시): Refresh Token이 탈취되면, 공격자는 Access Token 재발급을 통해 장기간 사용자 계정에 접근할 수 있게 됩니다. 따라서 Refresh Token은 매우 민감하게 다루어져야 합니다.
  • 추가적인 서버 부담: Refresh Token의 유효성을 검사하고 Access Token을 재발급하는 과정은 서버에 추가적인 부하를 줄 수 있습니다.
  • 복잡한 구현: 두 가지 토큰을 함께 관리하고 토큰 회전과 같은 보안 기능을 구현하는 것은 개발 복잡도를 증가시킵니다.

Refresh TokenAccess Token의 한계를 보완하고, Access Token Refresh Token 보안 및 사용자 편의성이라는 두 가지 목표를 동시에 달성하기 위한 필수적인 요소입니다. 다음 섹션에서는 이 두 토큰이 어떻게 유기적으로 동작하는지 구체적인 흐름을 통해 알아보겠습니다.


Access Token & Refresh Token 동작 방식: 실제 인증 흐름 완벽 이해

Access TokenRefresh Token은 개별적으로도 중요하지만, 이 둘이 어떻게 Access Token Refresh Token 동작 방식을 통해 상호작용하며 견고한 인증 시스템을 구축하는지 이해하는 것이 가장 중요합니다. 마치 춤추는 파트너처럼, 각자의 역할과 타이밍에 맞춰 움직이며 전체 시스템의 안정성과 보안을 책임집니다. 이제 실제 사용자의 로그인부터 API 요청, 그리고 Access Token 재발급에 이르는 전 과정을 시나리오와 코드를 통해 상세히 살펴보겠습니다.

1. 전체 인증 흐름 개요

Access TokenRefresh Token을 활용한 사용자 인증 흐름은 크게 다음과 같은 단계로 구성됩니다.

  1. 사용자 로그인: 사용자가 아이디와 비밀번호를 입력하여 인증을 시도합니다.
  2. 초기 토큰 발급: 서버는 로그인 성공 시 Access TokenRefresh Token을 동시에 발급합니다.
  3. API 요청: 클라이언트는 Access Token을 사용하여 보호된 API 요청을 서버로 보냅니다.
  4. Access Token 만료 처리: Access Token이 만료되면, API 요청은 실패합니다.
  5. Refresh Token으로 Access Token 재발급: 클라이언트는 Refresh Token을 사용하여 새로운 Access Token을 요청합니다.
  6. API 요청 재시도: 새로 발급받은 Access Token으로 API 요청을 다시 시도합니다.

2. 단계별 동작 원리 상세 설명

반응형

단계 1: 사용자 로그인 및 초기 토큰 발급

  • 클라이언트 → 서버: 사용자는 로그인 페이지에서 ID와 비밀번호를 입력하고 '로그인' 버튼을 클릭합니다. 클라이언트는 이 정보를 HTTPS를 통해 서버로 전송합니다.
  • 서버 동작:
    • 서버는 전송받은 ID와 비밀번호를 데이터베이스에 저장된 정보와 비교하여 사용자를 인증합니다.
    • 인증이 성공하면, 서버는 사용자 ID, 권한 등의 정보를 포함하는 Access TokenAccess Token 재발급을 위한 Refresh Token을 생성합니다.
    • Access Token짧은 유효 기간을 가지도록 설정하고, Refresh Token장기 유효 기간을 가지도록 설정합니다.
    • 서버는 이 두 토큰을 클라이언트에게 응답으로 보냅니다. Access Token은 응답 본문(JSON)에 포함될 수 있고, Refresh TokenHttpOnlySecure 속성이 설정된 쿠키에 담겨 전달되는 것이 일반적입니다.
  • 클라이언트 동작:
    • 클라이언트는 Access Token메모리(휘발성 상태)에 저장하거나, 경우에 따라 Local Storage, Session Storage 등 클라이언트가 접근 가능한 저장소에 저장합니다. (단, Local StorageSession StorageXSS 공격에 취약할 수 있으므로 주의해야 합니다. 이에 대한 자세한 내용은 '두 토큰, 안전하게 관리하는 법' 섹션을 참고하세요.)
    • Refresh Token은 자동으로 HttpOnly 쿠키에 저장됩니다(브라우저가 처리).

단계 2: Access Token을 이용한 API 요청

  • 클라이언트 → 서버: 사용자가 로그인 후 특정 데이터를 조회하거나 기능을 사용하기 위해 API 요청을 보낼 때마다, 클라이언트는 저장된 Access Token을 요청 헤더의 Authorization 필드에 Bearer 스키마와 함께 담아 서버로 전송합니다.
    GET /api/data/protected HTTP/1.1
    Host: example.com
    Authorization: Bearer <Access_Token_문자열>
  • 서버 동작:
    • 서버는 API 요청을 받으면 Authorization 헤더에서 Access Token을 추출합니다.
    • Access Token의 서명(Signature)이 유효한지 검증하고, 토큰 내부에 포함된 만료 시간(exp 클레임)이 경과했는지 확인합니다.
    • 만약 토큰이 유효하고 만료되지 않았다면, 서버는 Access Token의 페이로드(Payload)에서 사용자 정보와 권한을 추출하여 요청된 리소스에 접근할 수 있는지 판단한 후 요청을 처리하고 응답을 반환합니다.

단계 3: Access Token 만료 및 Refresh Token을 이용한 재발급

  • 클라이언트 → 서버: 시간이 흘러 Access Token짧은 유효 기간이 만료되었습니다. 클라이언트는 여전히 만료된 Access Token을 사용하여 API 요청을 보냅니다.
  • 서버 동작:
    • 서버는 Access Token의 유효성을 검사하는 과정에서 토큰이 만료되었음을 감지합니다.
    • 서버는 401 Unauthorized 또는 특정 에러 코드를 포함한 응답을 클라이언트에게 반환하여 Access Token이 더 이상 유효하지 않음을 알립니다.
  • 클라이언트 동작 (Access Token 재발급 로직):
    • 클라이언트는 401 응답을 받으면 Access Token이 만료되었음을 인지합니다.
    • 클라이언트는 Refresh Token을 사용하여 새로운 Access Token을 요청하는 특정 엔드포인트(POST /auth/refresh 등)로 요청을 보냅니다. 이때 Refresh TokenHttpOnly 쿠키에 저장되어 있으므로, 브라우저가 자동으로 요청에 포함하여 전송합니다. (JavaScript 코드에서는 직접 접근할 수 없으므로, XHR/Fetch API 요청 시 credentials: 'include' 옵션 등을 사용하여 쿠키가 포함되도록 합니다.)
  • 서버 동작 (인증 서버):
    • 인증 서버는 Refresh Token을 받으면 다음을 수행합니다.
      • 유효성 검증: Refresh Token의 서명, 만료 여부, 그리고 서버 측 데이터베이스에 저장된 Refresh Token과 일치하는지 확인합니다. (탈취된 Refresh Token인지 검사하는 중요한 단계입니다.)
      • 토큰 회전(Token Rotation) (선택 사항): Refresh Token을 재사용하지 못하도록 사용된 Refresh Token을 즉시 무효화하고, 새로운 Refresh Token을 발급하여 보안 취약점을 더욱 줄입니다.
      • Access Token 발급: 모든 검증이 성공하면, 새로운 Access Token을 생성하여 클라이언트에게 응답으로 보냅니다. (새로운 Refresh Token도 함께 보낼 수 있습니다.)
  • 클라이언트 동작 (재시도):
    • 클라이언트는 서버로부터 새로운 Access Token을 받으면, 이 토큰을 이전에 Access Token을 저장했던 위치에 업데이트합니다.
    • 이후, 클라이언트는 실패했던 원래의 API 요청을 새로 발급받은 Access Token을 사용하여 다시 시도합니다.

이러한 Access Token Refresh Token 동작 방식Access Token Refresh Token 차이점을 활용하여 Access Token짧은 유효 기간으로 인한 보안 이점을 유지하면서도, Refresh Token을 통해 Access Token 재발급을 자동화하여 사용자 편의성을 극대화합니다.

3. 클라이언트 측 Refresh Token 처리 Pseudocode (JavaScript)

아래는 클라이언트 측에서 Access Token 만료를 감지하고 Refresh Token을 사용하여 재발급하는 로직의 예시입니다.

// 가상의 API 호출 함수
async function callApi(url, method = 'GET', data = null) {
    // 주의: Access Token을 Local Storage에 저장하는 것은 XSS 공격에 취약합니다.
    // 더 안전한 방법으로는 메모리(휘발성 상태)에 저장하거나, HttpOnly 쿠키를 사용하는 것을 고려해야 합니다.
    let accessToken = localStorage.getItem('accessToken'); 

    try {
        const response = await fetch(url, {
            method: method,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${accessToken}`
            },
            body: data ? JSON.stringify(data) : null,
            // Refresh Token은 HttpOnly 쿠키에 저장될 것이므로,
            // 이 요청이 refresh endpoint로 갈 때 브라우저가 자동으로 포함하도록 합니다.
            // 하지만 일반 API 요청에서는 보통 credentials: 'same-origin' 또는 'omit' 입니다.
            // 만약 refresh 요청이 별도의 origin이나 sub-domain으로 간다면 credentials: 'include'가 필요할 수 있습니다.
        });

        if (response.status === 401) {
            // Access Token 만료 또는 유효하지 않음
            console.log("Access Token 만료! Refresh Token으로 재발급 시도...");
            const newAccessToken = await refreshAccessToken();
            if (newAccessToken) {
                // 새 Access Token으로 재시도
                console.log("Access Token 재발급 성공, API 재요청...");
                // Access Token을 Local Storage에 저장하는 것은 XSS 공격에 취약할 수 있습니다.
                // 보안 강화를 위해 메모리(휘발성 상태) 저장 또는 HttpOnly 쿠키 사용을 고려하세요.
                localStorage.setItem('accessToken', newAccessToken); // 새 토큰 저장 (예시)
                return await callApi(url, method, data); // 원래 요청 재시도
            } else {
                console.error("Refresh Token으로 Access Token 재발급 실패. 다시 로그인하세요.");
                // 사용자 로그아웃 처리 또는 로그인 페이지로 리다이렉트
                window.location.href = '/login'; // 예시
                return null;
            }
        }

        if (!response.ok) {
            const errorData = await response.json();
            throw new Error(errorData.message || 'API 요청 실패');
        }

        return await response.json();

    } catch (error) {
        console.error("API 요청 중 에러 발생:", error);
        throw error;
    }
}

// Access Token 재발급을 위한 함수
async function refreshAccessToken() {
    try {
        // Refresh Token은 HttpOnly 쿠키에 있으므로, 브라우저가 자동으로 보냄
        const response = await fetch('/auth/refresh-token', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            // HttpOnly 쿠키를 포함하여 보내기 위해 credentials: 'include' 필요
            credentials: 'include' 
        });

        if (!response.ok) {
            // Refresh Token 만료 또는 유효하지 않음
            throw new Error("Refresh Token이 유효하지 않거나 만료되었습니다.");
        }

        const data = await response.json();
        console.log("새로운 Access Token 수신:", data.accessToken);
        // 서버에서 새로운 Refresh Token을 보내준다면 함께 업데이트
        // if (data.refreshToken) {
        //     // HttpOnly 쿠키는 JS에서 직접 설정 불가. 서버 응답 헤더로 설정
        // }
        return data.accessToken;

    } catch (error) {
        console.error("Access Token 재발급 중 에러:", error);
        return null;
    }
}

// 사용 예시
async function fetchData() {
    try {
        const result = await callApi('/api/user/profile');
        if (result) {
            console.log("사용자 프로필:", result);
        }
    } catch (error) {
        console.error("프로필 데이터를 가져오지 못했습니다.");
    }
}

// 페이지 로드 시 Access Token이 없다면, refresh token으로 시도하거나 로그인 페이지로 리다이렉트
// 실제 구현에서는 초기 로그인 시 Access Token을 받아오는 로직이 먼저 있어야 합니다.
// fetchData(); 

Access Token Refresh Token 보안: 치명적인 취약점과 필수 방어 전략

Access TokenRefresh Token은 현대 인증 시스템의 중추를 이루지만, 완벽하게 안전한 시스템은 존재하지 않습니다. 이 두 토큰이 보안 취약점에 노출되면 심각한 문제가 발생할 수 있으며, Refresh Token 탈취는 사용자 계정 전체의 보안을 위협할 수 있습니다. 따라서 이들을 안전하게 관리하고 Access Token Refresh Token 보안을 강화하는 전략은 매우 중요합니다.

1. 토큰 탈취 위험성: XSS, CSRF 공격과 그 파급력

토큰 기반 인증 시스템에서 가장 큰 보안 취약점은 바로 토큰 탈취(Theft)입니다.

  • XSS (Cross-Site Scripting) 공격: 웹 애플리케이션에 악성 스크립트를 주입하여 사용자 브라우저에서 실행되도록 하는 공격입니다. 만약 Access TokenLocal Storage에 저장되어 있다면, XSS 공격자는 악성 스크립트를 통해 Local Storage에 접근하여 Access Token을 쉽게 탈취할 수 있습니다. 탈취된 Access Token으로 공격자는 유효 기간 동안 사용자 권한으로 API 요청을 보낼 수 있습니다.
  • CSRF (Cross-Site Request Forgery) 공격: 사용자가 의도하지 않은 요청을 강제로 보내게 만드는 공격입니다. Refresh TokenHttpOnly가 아닌 일반 쿠키에 저장되어 있다면, CSRF 공격에 취약해질 수 있습니다. 공격자가 악성 웹사이트를 통해 사용자의 브라우저로 Refresh Token이 포함된 Access Token 재발급 요청을 보내게 할 수 있습니다.

Refresh Token 탈취Access Token 탈취보다 훨씬 더 위험합니다. Access Token짧은 유효 기간을 가지므로 피해가 제한적이지만, Refresh Token장기 유효 기간을 가지므로 탈취되면 공격자가 지속적으로 새로운 Access Token을 발급받아 사용자 계정에 영구적으로 접근할 수 있기 때문입니다. 이는 사용자 계정의 모든 권한을 잃는 것과 같습니다.

2. 필수 방어 전략: HTTPS 적용

가장 기본적인 방어 전략이자 필수 사항은 바로 HTTPS 적용입니다. HTTP는 평문으로 데이터를 전송하기 때문에, 네트워크 중간에서 공격자가 Access Token이나 Refresh Token을 쉽게 가로챌 수 있습니다. HTTPS(Hypertext Transfer Protocol Secure)는 SSL/TLS 프로토콜을 사용하여 클라이언트와 서버 간의 통신을 암호화함으로써, 데이터 탈취 및 변조를 방지합니다. 모든 API 통신, 특히 로그인 및 토큰 발급/재발급 요청은 반드시 HTTPS를 통해서 이루어져야 합니다.

3. 토큰 저장 위치: HttpOnly Cookie vs Local Storage 논쟁

Access TokenRefresh Token을 클라이언트 측에 어디에 저장할 것인가는 Access Token Refresh Token 보안에 있어 매우 중요한 논쟁거리입니다.

가. Refresh Token: HttpOnly Cookie에 저장 (강력 권장)

  • HttpOnly 속성: Refresh TokenHttpOnly 속성을 가진 쿠키에 저장하는 것이 강력히 권장됩니다. HttpOnly 쿠키는 JavaScript를 통한 접근을 원천적으로 차단합니다. 이는 XSS 공격으로부터 Refresh Token 탈취를 막는 가장 효과적인 방법입니다. Refresh Token장기 유효 기간을 가지므로, XSS에 취약하게 만들어서는 안 됩니다.
  • Secure 속성: HTTPS 통신에서만 쿠키가 전송되도록 Secure 속성을 설정해야 합니다.
  • SameSite 속성: CSRF 공격을 방지하기 위해 SameSite=Lax 또는 SameSite=Strict 속성을 설정하여, 동일한 도메인에서 시작된 요청에만 쿠키가 전송되도록 제한합니다.
  • 장점: XSS 공격으로부터 Refresh Token을 안전하게 보호합니다. CSRF 공격 방지에도 기여합니다.
  • 단점: JavaScript에서 직접 Refresh Token에 접근할 수 없으므로, 클라이언트 측 로직 구현이 약간 복잡해질 수 있습니다 (브라우저가 자동으로 전송하도록 위임). Refresh Token 만료 시 서버에서 HttpOnly 쿠키를 지우는 로직이 필요합니다.

나. Access Token: Local Storage vs Session Storage vs HttpOnly Cookie

Access Token짧은 유효 기간을 가지므로 Refresh Token만큼 HttpOnly의 중요성이 크지는 않지만, 여전히 고민이 필요합니다.

  • Local Storage/Session Storage:
    • 장점: JavaScript에서 접근하기 매우 편리하여 개발 편의성이 높습니다.
    • 단점: XSS 공격에 매우 취약합니다. 악성 스크립트가 실행되면 Local Storage에 저장된 Access Token을 쉽게 탈취할 수 있습니다.
    • 권장: Access TokenLocal Storage에 저장하는 것은 XSS 공격에 대한 충분한 방어 메커니즘이 확보되지 않은 경우 권장되지 않습니다. 만약 사용해야 한다면, 강력한 XSS 방어 (콘텐츠 보안 정책(CSP), 입력값 검증 및 살균)가 필수적입니다.
  • 메모리(휘발성 상태):
    • 장점: 웹 페이지가 닫히거나 새로고침 될 때 토큰이 사라지므로, XSS 공격으로부터 상대적으로 안전합니다.
    • 단점: 새로고침 시 토큰이 사라져 Refresh Token을 이용한 재발급 로직이 반드시 필요하며, SPA(Single Page Application)에서 상태 관리가 복잡해질 수 있습니다.
  • HttpOnly Cookie:
    • Access Token 역시 HttpOnly 쿠키에 저장할 수 있습니다.
    • 장점: XSS 공격으로부터 Access Token을 효과적으로 보호합니다.
    • 단점: Access Token은 매 API 요청Authorization 헤더에 포함되어야 하는데, HttpOnly 쿠키에 있으면 JavaScript에서 직접 헤더에 추가할 수 없습니다. 따라서 백엔드에서 쿠키를 읽어 Authorization 헤더를 구성하는 리버스 프록시(Reverse Proxy) 또는 API 게이트웨이 등의 추가적인 설정이 필요합니다. 이는 클라이언트-서버 간 결합도를 높여 개발 복잡도를 증가시킬 수 있습니다.

종합 권장 사항:
일반적인 웹 환경에서는 Refresh TokenHttpOnly, Secure, SameSite 속성을 가진 쿠키에 저장하고, Access Token메모리(SPA의 경우 휘발성 상태)에 저장하거나, 더 나아가 Access Token 역시 HttpOnly 쿠키에 저장하고 서버 측에서 이를 처리하는 방식으로 구현하는 것이 XSS 공격으로부터의 Access Token Refresh Token 보안을 높이는 방법으로 여겨집니다.

4. 기타 보안 강화 전략

  • 토큰 무효화(Revocation): Refresh Token은 서버 측 데이터베이스에 저장되므로, 사용자가 로그아웃하거나 관리자가 강제로 특정 Refresh Token을 무효화할 수 있어야 합니다. Refresh Token이 탈취되었을 때 즉시 무효화하여 공격자의 접근을 차단할 수 있도록 하는 것이 중요합니다.
  • 토큰 회전(Rotation): Refresh Token을 한 번 사용하면 폐기하고 새로운 Refresh Token을 발급하는 전략입니다. 이는 Refresh Token이 중간에 탈취되더라도, 공격자가 한 번 사용하면 무효화되어 더 이상 사용할 수 없게 만들어 Refresh Token 탈취의 위험을 크게 줄입니다.
  • 비정상적인 접근 탐지: Refresh Token 사용 요청 시, 이전에 발급된 클라이언트의 IP 주소, 사용자 에이전트 정보, 위치 등의 변경 사항을 감지하여 의심스러운 활동을 식별하고 Access Token 재발급을 거부하거나 추가 인증을 요구할 수 있습니다.
  • 속도 제한(Rate Limiting): Access Token 재발급 엔드포인트에 속도 제한(Rate Limiting)을 적용하여 무차별 대입(Brute-force) 공격을 방지합니다.
  • 콘텐츠 보안 정책 (CSP): XSS 공격의 영향을 최소화하기 위해 Content Security Policy를 강력하게 설정하여, 특정 출처에서만 스크립트가 실행되도록 제한합니다.

Access Token Refresh Token 보안은 단순한 기능 구현을 넘어, 잠재적인 보안 취약점을 이해하고 다층적인 방어 전략을 적용하는 복합적인 과정입니다. 개발자와 시스템 관리자는 이러한 요소들을 신중하게 고려하여 서비스의 안전을 확보해야 합니다.


OAuth 2.0과 함께하는 Access Token & Refresh Token: 실제 활용 사례

이제 Access TokenRefresh Token이 어떻게 동작하고 관리되어야 하는지 충분히 이해하셨을 것입니다. 이 두 토큰은 개별적인 인증 시스템뿐만 아니라, OAuth 2.0과 같은 표준 인증 프레임워크에서도 핵심적인 역할을 수행합니다. OAuth 2.0은 직접적인 사용자 인증 방식이라기보다는 '권한 부여(Authorization)'를 위한 프레임워크로, 사용자 대신 리소스에 접근할 수 있는 권한을 제3자 애플리케이션에 위임하는 데 사용됩니다. 즉, "내가 너의 구글 계정에 접근하여 프로필 정보를 가져가도 될까?"와 같은 상황에서 Access Token이 사용되는 것입니다.

1. OAuth 2.0 이란? 권한 위임의 표준

OAuth 2.0은 사용자가 자신의 비밀번호를 제3자 서비스에 알려주지 않고도, 해당 서비스가 사용자의 리소스(예: 구글 드라이브 파일, 페이스북 프로필)에 접근할 수 있도록 권한을 부여하는 표준입니다. 이 과정에서 Access TokenRefresh Token이 핵심적인 매개체로 활용됩니다.

예를 들어, 여러분이 어떤 사진 편집 앱을 사용하는데, 이 앱이 구글 포토에 저장된 사진에 접근하고 싶어 한다고 가정해봅시다. OAuth 2.0을 사용하면 다음과 같은 흐름으로 진행됩니다.

  1. 사용자 동의 요청: 사진 앱이 "구글 포토의 사진에 접근해도 될까요?"라고 묻습니다.
  2. 구글 로그인 및 동의: 사용자는 구글 로그인 페이지로 리다이렉트되어 구글에 직접 로그인하고, 해당 앱에 대한 접근 권한을 동의합니다.
  3. 권한 부여 코드 발급: 구글은 사용자의 동의를 확인한 후, 앱에게 권한 부여 코드(Authorization Code)를 발급합니다.
  4. 권한 부여 코드 교환: 앱은 이 권한 부여 코드를 구글 서버로 전송하여 Access TokenRefresh Token을 요청합니다. 이때 앱은 클라이언트 ID와 시크릿(비밀 값)으로 자신의 신원을 인증합니다.
  5. 토큰 발급: 구글 서버는 권한 부여 코드가 유효하고 앱의 신원이 확인되면, Access TokenRefresh Token을 발급하여 앱에 전달합니다.

이제 사진 앱은 발급받은 Access Token을 사용하여 사용자의 구글 포토에 API 요청을 보내 사진을 가져올 수 있게 됩니다.

2. OAuth 2.0에서 Access Token & Refresh Token의 활용

  • Access Token: OAuth 2.0 Access Token Refresh Token의 핵심인 Access Token은 제3자 앱이 사용자의 리소스에 접근할 수 있는 단기적인 권한을 부여합니다. Access Token짧은 유효 기간은 앱이 토큰을 탈취당하더라도 피해를 최소화하기 위함입니다.
  • Refresh Token: Refresh TokenAccess Token이 만료되었을 때, 사용자에게 재로그인을 요구하지 않고도 새로운 Access Token을 발급받을 수 있도록 해줍니다. OAuth 2.0에서는 특히 사용자가 한 번 동의한 후에는 앱이 Refresh Token을 사용하여 Access Token 재발급을 통해 지속적으로 리소스에 접근할 수 있도록 하는 데 중요한 역할을 합니다. Refresh Token 역시 장기 유효 기간을 가지므로, Refresh Token 탈취OAuth 2.0 환경에서도 매우 치명적인 보안 취약점입니다.

3. 실제 서비스 적용 사례

  • 구글(Google), 페이스북(Facebook) 로그인 연동: 여러분이 다른 웹사이트나 앱에서 'Google로 로그인' 또는 'Facebook으로 로그인' 기능을 사용한다면, 그것이 바로 OAuth 2.0권한 부여 흐름을 따르는 것입니다. 이 과정에서 여러분의 계정에 대한 Access Token이 해당 서비스에 발급되어, 여러분의 프로필 정보나 이메일 주소 등에 접근할 수 있게 됩니다.
  • API 게이트웨이 및 마이크로서비스: 복잡한 마이크로서비스 아키텍처에서는 API 게이트웨이Access Token을 검증하고, 이를 통해 내부 서비스로 요청을 라우팅하는 역할을 합니다. Access Token의 무상태성 덕분에 각 마이크로서비스는 자체적으로 Access Token을 검증할 수 있어 유연하고 확장 가능한 시스템 구축이 가능합니다.
  • 모바일 앱 백엔드 연동: 모바일 앱은 사용자가 한 번 로그인하면 Refresh Token을 안전하게 저장하고, 이를 통해 Access Token 재발급을 주기적으로 수행하여 사용자가 앱을 장기간 편리하게 사용할 수 있도록 합니다.

OAuth 2.0Access Token, Refresh Token은 서로 밀접하게 연결되어 있으며, 현대 디지털 서비스에서 사용자 인증 방식권한 부여의 표준으로 자리매김했습니다. 이를 통해 사용자들은 더욱 편리하고 안전하게 다양한 서비스를 이용할 수 있게 되었으며, 개발자들은 복잡한 인증 로직을 효율적으로 구축할 수 있게 되었습니다.


결론적으로, Access TokenRefresh Token은 현대 웹 및 앱 서비스의 안전한 사용자 인증을 위한 핵심적인 요소입니다. Access Token짧은 유효 기간으로 보안 취약점 발생 시 피해를 최소화하고, Refresh Token장기 유효 기간으로 Access Token 재발급을 통해 사용자 편의성을 제공합니다. 이 두 토큰의 Access Token Refresh Token 동작 방식Access Token Refresh Token 차이를 정확히 이해하고, Access Token Refresh Token 보안에 대한 깊이 있는 고려와 적절한 대응 전략을 적용하는 것이 무엇보다 중요합니다. 오늘 다룬 내용을 통해 여러분의 서비스가 더욱 견고하고 안전해지는 데 도움이 되기를 바랍니다.


참고 자료:


반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/01   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함
반응형