🔐 SSO 시스템의 서버 간 API 키 인증 완벽 가이드
https://github.com/onesound71/sso_server
"하나의 키로 모든 서비스에 접근하는 마법" - 서버 간 통신에서 SSO API 키를 활용한 자동화된 인증 시스템을 구축해보세요!
GitHub - onesound71/sso_server
Contribute to onesound71/sso_server development by creating an account on GitHub.
github.com
🎯 왜 서버 간 API 키 인증이 필요할까?
마이크로서비스 아키텍처에서 여러 서버가 서로 통신해야 하는 상황은 매우 흔합니다. 예를 들어:
- 주문 서비스가 결제 서비스에 결제 요청
- 사용자 서비스가 알림 서비스에 메시지 전송 요청
- API 게이트웨이가 인증 서버에 토큰 검증 요청
이때 매번 사용자 ID/비밀번호로 로그인할 수는 없습니다. 서버는 24시간 자동으로 동작해야 하고, 사람의 개입 없이 인증되어야 하죠.
바로 이런 상황에서 API 키 인증이 빛을 발합니다!
🏗️ 시스템 아키텍처 이해하기
전체 구조 한눈에 보기
graph TB
A[👤 사용자] --> B[🌐 브라우저 로그인]
C[🤖 클라이언트 앱] --> D[🔑 API 키 인증]
E[📱 Hello 서버<br/>포트: 5001] --> F[🔐 SSO 서버<br/>포트: 5002]
B --> E
D --> E
E --> G[서버 간 인증<br/>자동 키 교환]
F --> G
subgraph "인증 방식"
B
D
G
end
subgraph "보호된 리소스"
H[📊 대시보드]
I[🛡️ 관리자 페이지]
J[📈 API 엔드포인트]
end
E --> H
E --> I
E --> J
핵심 구성 요소
🔐 SSO 서버 (포트 5002)
- 역할: 중앙 인증 관리소
- 기능: API 키 생성, 검증, 세션 관리
- 비유: 은행의 본점 (모든 인증 결정)
📱 Hello 서버 (포트 5001)
- 역할: 보호된 애플리케이션
- 기능: 실제 서비스 제공, 인증 요청 전달
- 비유: 은행의 지점 (서비스 제공하되 본점에서 인증 확인)
🔑 API 키 생성: 마스터 키를 만드는 과정
1단계: 기본 개념 이해
API 키는 서버의 신분증과 같습니다. 사람이 주민등록증을 가지고 은행에서 신원을 증명하듯, 서버는 API 키로 자신의 정체성을 증명합니다.
API 키의 구조:
sso_key_hello_server_default_12345
│ │ │ │ │
│ │ │ │ └─ 고유 식별자
│ │ │ └─ 용도/환경
│ │ └─ 클라이언트명
│ └─ 서비스 타입
└─ 프로젝트 접두사
2단계: CLI 도구로 키 생성하기
기본 명령어 구조:
python api_key_manager.py [명령] [매개변수]
새로운 API 키 생성:
# 일반적인 앱용 키 생성
python api_key_manager.py generate my-mobile-app
# 출력 예시:
# ✅ API 키가 성공적으로 생성되었습니다!
# 클라이언트: my-mobile-app
# API 키: sso_key_my_mobile_app_7a9f2e8c1b5d
# 생성 시간: 2025-05-30 14:32:18
특별한 용도별 키 생성:
# 관리자 도구용
python api_key_manager.py generate admin-dashboard
# 결제 서비스용
python api_key_manager.py generate payment-service
# 데이터 분석 도구용
python api_key_manager.py generate analytics-bot
3단계: 생성된 키 관리하기
등록된 모든 키 확인:
python api_key_manager.py list
# 출력 예시:
# 📋 등록된 API 키 목록:
#
# 1. 클라이언트: hello_server_default
# 키: sso_key_hello_server_default_12345
# 상태: ✅ 활성
#
# 2. 클라이언트: my-mobile-app
# 키: sso_key_my_mobile_app_7a9f2e8c1b5d
# 상태: ✅ 활성
#
# 총 2개의 API 키가 등록되어 있습니다.
특정 키 테스트:
python api_key_manager.py test sso_key_my_mobile_app_7a9f2e8c1b5d
# 출력 예시:
# 🔍 API 키 테스트 중...
#
# ✅ API 키 검증 성공!
# 📊 테스트 결과:
# 클라이언트: my-mobile-app
# 인증 상태: 유효
# 응답 시간: 23ms
# 서버 연결: 정상
🤝 서버 간 인증: 자동화된 신뢰 관계 구축
핵심 아이디어: 신뢰의 체인
서버 간 인증은 "친구의 친구는 친구" 개념과 비슷합니다:
- Hello 서버가 SSO 서버를 신뢰
- SSO 서버가 API 키 소유자를 인증
- 따라서 Hello 서버도 API 키 소유자를 신뢰
자동 인증 흐름 상세 분석
# Hello 서버의 자동 인증 과정 (app.py에서)
class HelloServer:
def __init__(self):
self.sso_url = "http://localhost:5002"
self.server_key = "sso_key_hello_server_default_12345"
self.session_token = None
def authenticate_with_sso(self):
"""SSO 서버와 자동 인증"""
try:
response = requests.post(
f"{self.sso_url}/api/authenticate",
headers={"X-API-Key": self.server_key},
json={"api_key": self.server_key}
)
if response.status_code == 200:
data = response.json()
self.session_token = data.get('session_token')
print(f"✅ SSO 서버와 인증 성공: {self.session_token}")
return True
else:
print(f"❌ SSO 인증 실패: {response.status_code}")
return False
except Exception as e:
print(f"🚨 SSO 연결 오류: {e}")
return False
실제 동작 과정 단계별 분석
1단계: Hello 서버 시작
cd hello-server
python app.py
# 콘솔 출력:
# 🚀 Hello 서버 시작 중...
# 🔗 SSO 서버 연결 시도: http://localhost:5002
# 🔑 서버 키로 인증 중: sso_key_hello_server_default_12345
# ✅ SSO 서버와 인증 성공: session_abc123xyz789
# 🌐 서버 실행 중: http://localhost:5001
2단계: 클라이언트 요청 처리
@app.route('/')
def portal():
# 1. API 키 확인
api_key = request.args.get('api_key') or request.headers.get('X-API-Key')
if api_key:
# 2. SSO 서버에 API 키 검증 요청
if validate_api_key_with_sso(api_key):
# 3. 검증 성공 시 포털 페이지 제공
return render_template('portal.html',
auth_method='API Key',
client_info=get_client_info(api_key))
# 4. API 키 없으면 브라우저 세션 확인
return check_browser_session()
3단계: SSO 서버에서의 검증
@app.route('/api/authenticate', methods=['POST'])
def authenticate_api_key():
api_key = request.headers.get('X-API-Key')
# 1. 키 유효성 검증
if api_key in valid_api_keys:
# 2. 세션 토큰 생성
session_token = generate_session_token()
# 3. 클라이언트 정보 반환
return jsonify({
'status': 'success',
'session_token': session_token,
'client_name': valid_api_keys[api_key],
'expires_in': 3600
})
return jsonify({'status': 'error', 'message': 'Invalid API key'}), 401
💻 실전 사용 예시: 다양한 시나리오
시나리오 1: 모바일 앱에서 API 접근
앱 개발자 입장:
// React Native 앱에서
class ApiClient {
constructor() {
this.apiKey = 'sso_key_my_mobile_app_7a9f2e8c1b5d';
this.baseUrl = 'http://your-server.com:5001';
}
async fetchUserData() {
try {
const response = await fetch(`${this.baseUrl}/api/user`, {
headers: {
'X-API-Key': this.apiKey,
'Content-Type': 'application/json'
}
});
if (response.ok) {
return await response.json();
} else {
throw new Error('API 인증 실패');
}
} catch (error) {
console.error('API 요청 실패:', error);
}
}
}
시나리오 2: 마이크로서비스 간 통신
주문 서비스 → 결제 서비스:
# order_service.py
class OrderService:
def __init__(self):
self.payment_api_key = "sso_key_payment_service_prod_xyz789"
self.payment_url = "http://payment-service:5001"
def process_payment(self, order_id, amount):
headers = {
'X-API-Key': self.payment_api_key,
'Content-Type': 'application/json'
}
payload = {
'order_id': order_id,
'amount': amount,
'currency': 'KRW'
}
response = requests.post(
f"{self.payment_url}/api/charge",
headers=headers,
json=payload
)
return response.json()
시나리오 3: 자동화 스크립트
배치 작업용 스크립트:
#!/bin/bash
# backup_script.sh
API_KEY="sso_key_backup_bot_daily_456"
SERVER_URL="http://localhost:5001"
# 1. API 키로 인증하여 데이터 추출
curl -H "X-API-Key: $API_KEY" \
"$SERVER_URL/api/export/daily-data" \
-o "backup_$(date +%Y%m%d).json"
# 2. 백업 완료 알림
curl -H "X-API-Key: $API_KEY" \
-X POST "$SERVER_URL/api/notifications" \
-d '{"message": "일일 백업 완료", "level": "info"}'
echo "✅ 백업 작업 완료: backup_$(date +%Y%m%d).json"
🔒 보안 모범 사례
1. API 키 보안 관리
❌ 절대 하지 말아야 할 것들:
// 클라이언트 사이드 코드에 직접 노출 (위험!)
const API_KEY = "sso_key_secret_123"; // ❌
// Git에 키를 커밋 (위험!)
config.js: apiKey: "sso_key_real_production_key" // ❌
// 로그에 키 출력 (위험!)
console.log(`Using API key: ${apiKey}`); // ❌
✅ 올바른 방법들:
# 환경 변수 사용
export SSO_API_KEY="sso_key_my_app_prod_xyz789"
# .env 파일 활용 (Git 제외)
echo "SSO_API_KEY=sso_key_my_app_prod_xyz789" > .env
echo ".env" >> .gitignore
# 컨테이너 시크릿 사용
kubectl create secret generic sso-key \
--from-literal=api-key="sso_key_k8s_app_secret"
2. 키 순환 및 만료 관리
# api_key_rotation.py - 키 자동 순환 예시
import schedule
import time
from datetime import datetime, timedelta
class ApiKeyRotator:
def __init__(self):
self.current_keys = {}
def rotate_key(self, client_name):
"""키 순환 로직"""
# 1. 새 키 생성
new_key = self.generate_new_key(client_name)
# 2. 기존 키와 병행 사용 (grace period)
old_key = self.current_keys.get(client_name)
# 3. 7일 후 기존 키 비활성화
schedule_key_deactivation(old_key, days=7)
# 4. 클라이언트에 새 키 알림
notify_client_new_key(client_name, new_key)
print(f"🔄 {client_name} 키 순환 완료: {new_key}")
# 매월 1일 자동 키 순환
schedule.every().month.do(lambda: ApiKeyRotator().rotate_key("mobile-app"))
3. 모니터링 및 로깅
# api_usage_monitor.py
class ApiUsageMonitor:
def log_api_request(self, api_key, endpoint, ip_address):
"""API 사용 로깅"""
log_entry = {
'timestamp': datetime.now().isoformat(),
'api_key_hash': hashlib.sha256(api_key.encode()).hexdigest()[:16],
'endpoint': endpoint,
'ip_address': ip_address,
'client_name': self.get_client_name(api_key)
}
# 보안 로그 저장
self.save_security_log(log_entry)
# 의심스러운 활동 감지
if self.detect_suspicious_activity(api_key, ip_address):
self.alert_security_team(log_entry)
def detect_suspicious_activity(self, api_key, ip_address):
"""비정상 활동 감지"""
# 1분에 100회 이상 요청
if self.get_request_count(api_key, minutes=1) > 100:
return True
# 새로운 IP에서의 접근
if not self.is_known_ip(api_key, ip_address):
return True
return False
🛠️ 고급 설정 및 커스터마이징
커스텀 API 키 형식 정의
# custom_key_generator.py
import secrets
import string
from datetime import datetime
class CustomKeyGenerator:
def __init__(self, prefix="mycompany"):
self.prefix = prefix
def generate_key(self, client_name, environment="prod"):
"""커스텀 형식의 API 키 생성"""
# 타임스탬프 기반 식별자
timestamp = datetime.now().strftime("%Y%m%d")
# 랜덤 문자열 생성
random_part = ''.join(secrets.choice(
string.ascii_lowercase + string.digits
) for _ in range(12))
# 체크섬 계산
checksum = self.calculate_checksum(client_name, timestamp, random_part)
# 최종 키 조합
api_key = f"{self.prefix}_key_{environment}_{client_name}_{timestamp}_{random_part}_{checksum}"
return api_key
def calculate_checksum(self, *parts):
"""간단한 체크섬 계산"""
combined = ''.join(str(part) for part in parts)
return format(hash(combined) % 10000, '04d')
# 사용 예시
generator = CustomKeyGenerator("acme")
key = generator.generate_key("mobile-app", "staging")
# 결과: acme_key_staging_mobile_app_20250530_a7f9e2c8b1d5_7382
환경별 키 관리 시스템
# environment_manager.py
class EnvironmentKeyManager:
def __init__(self):
self.environments = {
'development': {
'prefix': 'dev',
'expiry_days': 30,
'rate_limit': 1000
},
'staging': {
'prefix': 'stage',
'expiry_days': 14,
'rate_limit': 500
},
'production': {
'prefix': 'prod',
'expiry_days': 90,
'rate_limit': 100
}
}
def create_environment_key(self, client_name, environment):
"""환경별 키 생성"""
if environment not in self.environments:
raise ValueError(f"지원하지 않는 환경: {environment}")
env_config = self.environments[environment]
key = f"sso_key_{env_config['prefix']}_{client_name}_{self.generate_random()}"
# 키 메타데이터 저장
self.save_key_metadata(key, {
'client_name': client_name,
'environment': environment,
'rate_limit': env_config['rate_limit'],
'expires_in_days': env_config['expiry_days'],
'created_at': datetime.now().isoformat()
})
return key
📊 성능 최적화 및 모니터링
인증 성능 최적화
# auth_cache.py
import redis
import json
from functools import wraps
class AuthCache:
def __init__(self):
self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
self.cache_ttl = 300 # 5분 캐시
def cached_auth(self, func):
"""인증 결과 캐싱 데코레이터"""
@wraps(func)
def wrapper(api_key, *args, **kwargs):
# 캐시에서 확인
cache_key = f"auth:{hashlib.md5(api_key.encode()).hexdigest()}"
cached_result = self.redis_client.get(cache_key)
if cached_result:
print(f"🚀 캐시 히트: {api_key[:20]}...")
return json.loads(cached_result)
# 캐시 미스 - 실제 인증 수행
result = func(api_key, *args, **kwargs)
# 결과 캐싱
if result.get('valid'):
self.redis_client.setex(
cache_key,
self.cache_ttl,
json.dumps(result)
)
print(f"💾 캐시 저장: {api_key[:20]}...")
return result
return wrapper
# 사용 예시
auth_cache = AuthCache()
@auth_cache.cached_auth
def validate_api_key(api_key):
"""API 키 검증 (캐싱 적용)"""
# 실제 검증 로직
return sso_server.authenticate(api_key)
실시간 메트릭 수집
# metrics_collector.py
from prometheus_client import Counter, Histogram, generate_latest
import time
# 메트릭 정의
auth_requests_total = Counter(
'sso_auth_requests_total',
'Total authentication requests',
['method', 'status']
)
auth_duration_seconds = Histogram(
'sso_auth_duration_seconds',
'Time spent on authentication'
)
class MetricsCollector:
@staticmethod
def record_auth_request(method, status, duration):
"""인증 요청 메트릭 기록"""
auth_requests_total.labels(method=method, status=status).inc()
auth_duration_seconds.observe(duration)
@staticmethod
def auth_metrics_middleware(func):
"""인증 메트릭 수집 미들웨어"""
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
try:
result = func(*args, **kwargs)
status = 'success' if result.get('valid') else 'failed'
except Exception as e:
status = 'error'
raise
finally:
duration = time.time() - start_time
method = 'api_key' # 또는 요청에서 추출
MetricsCollector.record_auth_request(method, status, duration)
return result
return wrapper
# 메트릭 엔드포인트
@app.route('/metrics')
def metrics():
return generate_latest(), 200, {'Content-Type': 'text/plain'}
🎯 마무리: SSO의 힘을 활용하세요!
핵심 요약
🔑 API 키 생성의 핵심:
- 명확한 명명 규칙:
prefix_type_client_identifier
- 환경별 관리: dev, staging, prod 분리
- 정기적 순환: 보안을 위한 키 교체
🤝 서버 간 인증의 핵심:
- 자동화된 신뢰: 한 번 설정하면 계속 동작
- 토큰 기반 세션: 효율적인 연결 유지
- 장애 복구: 연결 끊김 시 자동 재연결
다음 단계 제안
- 보안 강화: JWT 토큰 도입, 키 암호화
- 확장성 개선: 로드 밸런서, 분산 캐시
- 모니터링 고도화: 실시간 대시보드, 알림 시스템
마지막 팁: 시작은 간단하게, 발전은 단계적으로! 기본 API 키 시스템부터 구축한 후 필요에 따라 고급 기능을 추가해나가세요.
'나의 IT 기억' 카테고리의 다른 글
Redis 말고 공유 메모리? 웹서버 여러대에서 증권 시세를 빠르게 공유하는 새로운 방법 (0) | 2025.06.01 |
---|---|
🎯iframe 없이 서버 간 웹 페이지 통합하기: HTML Fetch 방식으로 마이크로서비스 UI 통합 (0) | 2025.05.31 |
💼 업무 자동화 전쟁: n8n vs Langflow vs Make - 당신의 팀에겐 어떤 무기가 필요할까? (6) | 2025.05.29 |
🤖 그냥해보세요! Qwen2.5VL 멀티모달 모델로 이미지 분석하기 (0) | 2025.05.28 |
🎯 스타트업이 놓치기 쉬운 DB 리플리케이션 - 사용자 급증에 대비하라 (1) | 2025.05.28 |