간편 API: 토큰 자동 갱신, OAuth 2.0 한방에 끝내기
몇 달 전 개인 프로젝트로 개발 중인 서비스에 API 연동을 적용하면서, 예상치 못했던 토큰 관리 문제에 직면했습니다. 하지만 시행착오 끝에 토큰 자동 갱신 시스템을 구축하여 개발 생산성을 크게 향상시킬 수 있었습니다. 이 글에서는 API 연동 시 필수적인 JWT 토큰 자동 갱신과 OAuth 2.0 인증 과정을 간편하게 구현하는 방법을 소개하고, 실제 적용 과정에서 발생할 수 있는 문제점을 해결하는 데 도움이 되는 정보를 제공합니다.
액세스 토큰과 리프레시 토큰 이해

API를 사용하기 위해서는 인증 과정을 거쳐 토큰을 발급받아야 합니다. 일반적으로 액세스 토큰과 리프레시 토큰 두 종류의 토큰이 사용됩니다. 액세스 토큰은 API에 접근할 수 있는 권한을 부여하며, 리프레시 토큰은 만료된 액세스 토큰을 갱신하는 데 사용됩니다.
액세스 토큰은 비교적 짧은 만료 시간을 가지며, 리프레시 토큰은 액세스 토큰보다 긴 만료 시간을 가집니다. 예를 들어, 카카오 REST API의 액세스 토큰은 6시간, 리프레시 토큰은 2달 동안 유효합니다. 네이버 API의 경우, 액세스 토큰은 1시간, 리프레시 토큰은 3개월의 유효기간을 갖습니다.
OAuth 2.0 인증 흐름
OAuth 2.0은 API 접근 권한을 안전하게 위임하기 위한 산업 표준 인증 프로토콜입니다. 사용자 인증 및 권한 부여 과정을 단순화하고 보안성을 높이는 데 목적이 있습니다.
OAuth 2.0 인증 흐름은 다음과 같습니다.
- 클라이언트가 사용자에게 인증을 요청합니다.
- 사용자가 인증 서버에서 인증을 완료합니다.
- 인증 서버가 클라이언트에게 인증 코드를 발급합니다.
- 클라이언트가 인증 코드를 사용하여 액세스 토큰과 리프레시 토큰을 요청합니다.
- API 서버는 액세스 토큰을 사용하여 클라이언트의 요청을 처리합니다.
OAuth 2.0에는 여러 가지 권한 부여 방식이 있으며, 각 방식은 사용 사례에 따라 적합성이 다릅니다. 예를 들어, Authorization Code Grant 방식은 웹 애플리케이션에서 사용하기에 적합하며, Implicit Grant 방식은 JavaScript 기반의 싱글 페이지 애플리케이션(SPA)에 적합합니다. 각 방식의 장단점을 이해하고, 애플리케이션의 특성에 맞는 방식을 선택하는 것이 중요합니다.
토큰 자동 갱신 구현 전략
액세스 토큰이 만료되면 API 요청이 실패하므로, 토큰을 자동으로 갱신하는 메커니즘이 필요합니다. 리프레시 토큰을 사용하여 새 액세스 토큰을 요청하고, 기존 액세스 토큰을 대체하여 API 요청을 계속할 수 있습니다.
토큰 자동 갱신을 구현하는 방법은 다음과 같습니다.
- API 요청 시 액세스 토큰의 만료 여부를 확인합니다.
- 액세스 토큰이 만료되었다면, 리프레시 토큰을 사용하여 새 액세스 토큰을 요청합니다.
- 새 액세스 토큰을 발급받으면, 기존 액세스 토큰을 대체하고 API 요청을 재시도합니다.
- 리프레시 토큰 갱신 주기를 설정하여, 토큰 유효 기간을 늘립니다.
토큰 만료 여부를 확인하는 방법으로는, 액세스 토큰의 만료 시간을 저장해두고 현재 시간과 비교하는 방법이 있습니다. 또는, API 요청 시 401 Unauthorized 에러가 발생하면 액세스 토큰이 만료된 것으로 간주하고 갱신을 시도할 수도 있습니다.
Python을 이용한 토큰 자동 갱신 코드 예시
다음은 Python을 사용하여 액세스 토큰을 자동으로 갱신하는 간단한 예시 코드입니다.
import requests
import json
import time
# 토큰 정보 (실제 값으로 대체)
ACCESS_TOKEN = "your_access_token"
REFRESH_TOKEN = "your_refresh_token"
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"
TOKEN_URL = "your_token_url"
EXPIRY_TIME = time.time() + 3600 # 예: 1시간 후 만료
def refresh_token():
"""리프레시 토큰을 사용하여 새 액세스 토큰을 발급받습니다."""
global ACCESS_TOKEN, EXPIRY_TIME
data =
try:
response = requests.post(TOKEN_URL, data=data)
response.raise_for_status() # HTTP 에러 발생 시 예외 발생
except requests.exceptions.RequestException as e:
print("토큰 갱신 요청 실패:", e)
return False
response_json = response.json()
if response.status_code == 200:
ACCESS_TOKEN = response_json['access_token']
EXPIRY_TIME = time.time() + response_json.get('expires_in', 3600) # 만료 시간 갱신
print("액세스 토큰이 갱신되었습니다. 새로운 만료 시간:", EXPIRY_TIME)
return True
else:
print("토큰 갱신 실패:", response_json)
return False
def is_token_expired():
"""토큰 만료 여부를 확인합니다."""
return time.time() >= EXPIRY_TIME
def call_api(url):
"""API를 호출하고, 액세스 토큰이 만료되었을 경우 갱신을 시도합니다."""
global ACCESS_TOKEN
if is_token_expired():
print("액세스 토큰이 만료되었습니다.")
if not refresh_token():
print("토큰 갱신에 실패했습니다.")
return None
headers =
try:
response = requests.get(url, headers=headers)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print("API 요청 실패:", e)
return None
if response.status_code == 200:
return response.json()
else:
print("API 호출 실패:", response.status_code, response.text)
return None
# API 호출 예시
API_URL = "your_api_url"
result = call_api(API_URL)
if result:
print("API 결과:", result)
else:
print("API 호출 실패")
위 코드는 액세스 토큰이 만료되었을 때 리프레시 토큰을 사용하여 새 액세스 토큰을 발급받고, API 요청을 재시도하는 과정을 보여줍니다. 실제 서비스에서는 토큰을 안전하게 저장하고, 오류 처리 로직을 추가해야 합니다. 헷갈릴 수 있지만, 실제 적용 시에는 EXPIRY_TIME 변수를 적절히 관리하여 토큰 만료 시간을 정확하게 반영해야 합니다.
OAuth 2.0 트러블슈팅

OAuth 2.0을 구현하는 과정에서 다양한 문제가 발생할 수 있습니다. 흔히 발생하는 문제와 해결 방법은 다음과 같습니다.
API 인증 에러: API 서버가 클라이언트의 인증 정보를 인식하지 못하는 경우 발생합니다. 클라이언트 ID, 클라이언트 시크릿, 리다이렉트 URI 등의 설정이 정확한지 확인해야 합니다. 예를 들어, 클라이언트 시크릿이 잘못 입력되었거나, 리다이렉트 URI가 등록된 URI와 일치하지 않는 경우에 발생할 수 있습니다.
토큰 만료: 액세스 토큰이 만료되어 API 요청이 실패하는 경우 발생합니다. 토큰 자동 갱신 로직을 구현하여 액세스 토큰을 자동으로 갱신해야 합니다. 만약 자동 갱신 로직이 제대로 작동하지 않는다면, 리프레시 토큰이 만료되었거나, 리프레시 토큰이 유효하지 않은 상태일 수 있습니다.
리프레시 토큰 만료: 리프레시 토큰도 만료될 수 있습니다. 리프레시 토큰이 만료되면 사용자에게 다시 인증을 요청해야 합니다. 리프레시 토큰의 만료 기간은 API 제공 업체마다 다르므로, 해당 API의 문서를 확인하여 만료 기간을 확인해야 합니다.
Google OAuth 2.0 리프레시 토큰 제한: Google 계정당 OAuth 2.0 클라이언트 ID당 최대 100개의 리프레시 토큰 제한이 있습니다. 이 제한에 도달하면 새 리프레시 토큰을 생성할 때 가장 오래된 토큰이 자동으로 무효화됩니다. 따라서, 여러 기기 또는 애플리케이션에서 동일한 Google 계정을 사용하는 경우, 리프레시 토큰 제한에 도달할 수 있습니다.
이러한 문제들을 해결하기 위해서는 API 제공 업체의 문서를 꼼꼼하게 확인하고, 오류 로그를 분석하여 문제의 원인을 파악하는 것이 중요합니다.
보안 고려 사항
토큰은 중요한 정보이므로 안전하게 관리해야 합니다. 다음은 토큰을 안전하게 관리하기 위한 몇 가지 방법입니다.
HTTPS와 같은 안전한 채널을 통해 토큰을 전송합니다. HTTP 환경에서는 토큰이 탈취될 위험이 있으므로, 반드시 HTTPS를 사용해야 합니다.
HttpOnly 쿠키를 사용하여 토큰을 저장합니다. HttpOnly 쿠키는 JavaScript에서 접근할 수 없도록 설정되어 있어, XSS 공격으로부터 토큰을 보호할 수 있습니다.
액세스 토큰의 만료 시간을 짧게 설정하여 토큰 탈취 시 피해를 줄입니다. 액세스 토큰의 만료 시간을 짧게 설정하면, 토큰이 탈취되더라도 공격자가 사용할 수 있는 시간이 제한됩니다.
리프레시 토큰을 안전하게 저장합니다. 리프레시 토큰은 액세스 토큰을 갱신하는 데 사용되므로, 액세스 토큰보다 더 중요한 정보입니다. 리프레시 토큰은 안전한 저장소에 암호화하여 저장해야 합니다.
리프레시 토큰이 도난당했을 경우를 대비하여 서버에서 블랙리스트로 관리하여 무효화할 수 있습니다. 리프레시 토큰이 도난당한 경우, 해당 토큰을 블랙리스트에 추가하여 더 이상 사용할 수 없도록 해야 합니다.
권한 부여 서버가 리프레시 토큰이 두 번 사용된 것을 감지하면, 토큰이 복사되어 공격자가 사용하고 있을 가능성이 있으므로 관련된 모든 액세스 토큰과 리프레시 토큰을 즉시 취소할 수 있습니다.
- Google Workspace 환경에서 사용자 비밀번호를 변경하면 해당 계정으로 발급된 OAuth 2.0 토큰이 자동으로 취소됩니다. 이는 보안을 강화하기 위한 조치입니다.
| 보안 항목 | 설명 | 권장 조치 |
|---|---|---|
| 토큰 전송 채널 | 토큰은 네트워크를 통해 전송되므로, 탈취 위험이 존재합니다. | HTTPS를 사용하여 암호화된 채널을 통해 전송합니다. |
| 토큰 저장 위치 | 토큰이 저장되는 위치에 따라 보안 위험이 달라집니다. | HttpOnly 쿠키를 사용하여 JavaScript 접근을 제한합니다. |
| 토큰 만료 시간 | 토큰의 유효 기간이 길수록 탈취 시 피해가 커집니다. | 액세스 토큰 만료 시간을 짧게 설정합니다. |
| 리프레시 토큰 관리 | 리프레시 토큰은 액세스 토큰 갱신에 사용되므로, 안전하게 관리해야 합니다. | 암호화하여 안전한 저장소에 보관하고, 도난 시 블랙리스트에 등록합니다. |
정리하면, 토큰을 안전하게 관리하기 위해서는 전송 채널 암호화, 안전한 저장 위치 선정, 적절한 만료 시간 설정, 리프레시 토큰 관리 등의 조치가 필요합니다.
자동 포스팅 스크립트에 적용
구축된 토큰 자동 갱신 시스템을 블로그/SNS 자동 포스팅 스크립트에 적용하면, 토큰 만료로 인한 오류 없이 안정적으로 포스팅 작업을 수행할 수 있습니다. 스크립트 내에서 API 호출 전에 토큰 유효성을 검사하고, 만료되었을 경우 갱신하는 로직을 추가하면 됩니다.
자동 포스팅 스크립트에 적용 시 스크립트 실행 시 발생하는 오류 로그를 주기적으로 확인하여 토큰 갱신 과정에서 문제가 발생하는지 확인하는 것이 좋습니다.
결론

OAuth 2.0과 토큰 자동 갱신은 API 연동 개발에서 중요한 부분을 차지합니다. 안전하고 효율적인 API 연동을 위해, 오늘 설명드린 내용들을 잘 활용하시기 바랍니다.
판단 기준
API 연동 방식을 선택할 때, 보안 요구 사항이 높다면 OAuth 2.0을, 개발 편의성이 중요하다면 API 키 방식을 선택하는 것이 좋습니다. 토큰 자동 갱신 방식은 API의 종류와 사용 목적에 따라 적절한 방법을 선택해야 합니다.