feat(security): 관리자 API에 API 키 인증 적용

- 민감한 API(모델 재시작, 캐시 정리)를 보호하기 위해 API 키 기반 인증 추가
-  모듈을 생성하여 API 키 검증 로직 중앙화
- 에 API 키를 설정하여 관리 용이성 확보
- FastAPI의 를 사용하여 엔드포인트에 보안 규칙 적용
- CODING_CONVENTIONS.md에 API 키 사용법 및 cURL 예제 문서화
This commit is contained in:
hyungi
2025-07-25 07:02:39 +09:00
parent 867c7f4bca
commit 9093611c96
5 changed files with 98 additions and 5 deletions

View File

@@ -67,6 +67,20 @@
- **서버 포트**: `8080` (FastAPI 웹 서비스)
- **대시보드 접속**: `http://192.168.1.122:8080/dashboard`
### 6.1. API 키 인증
관리자용 API(`/api/restart-models`, `/api/clear-cache` 등)를 호출하려면 HTTP 요청 헤더에 API 키를 포함해야 합니다.
- **헤더 이름**: `X-API-KEY`
- **API 키 값**: `config/settings.json` 파일의 `security.api_key` 값을 사용합니다.
**cURL 예시:**
```bash
curl -X POST http://192.168.1.122:20080/api/restart-models \
-H "X-API-KEY: nllb-secret-key-!@#$%"
```
## 7. 서버 운영 및 배포 (macOS)
이 서버는 24/7 무중단 운영을 위해 macOS의 `launchd` 서비스를 통해 관리됩니다. 이를 통해 시스템 재부팅 시 자동 시작 및 예기치 않은 종료 시 자동 재시작이 보장됩니다.

View File

@@ -38,5 +38,8 @@
"static_hosting": "static-hosting",
"metadata": "metadata",
"local_work_path": "~/Scripts/nllb-translation-system"
},
"security": {
"api_key": "nllb-secret-key-!@#$%"
}
}

View File

@@ -7,3 +7,5 @@ INFO: 192.168.1.122:51436 - "GET /api/dashboard HTTP/1.1" 200 OK
KoBART 모델 로드 완료
모델 로딩 완료!
INFO: 192.168.1.122:51443 - "GET /api/dashboard HTTP/1.1" 200 OK
INFO: 192.168.1.122:51518 - "GET /api/dashboard HTTP/1.1" 200 OK
INFO: 192.168.1.122:51563 - "GET /api/dashboard HTTP/1.1" 200 OK

View File

@@ -5,7 +5,7 @@ Mac Mini FastAPI + DS1525+ 연동
"""
from contextlib import asynccontextmanager
from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks, Request
from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks, Request, Depends
from fastapi.templating import Jinja2Templates
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse, JSONResponse
@@ -23,6 +23,7 @@ import logging
# 중앙 설정 로더 및 AI 서비스 임포트
from config_loader import settings
from background_ai_service import ai_service
from security import get_api_key
# 로깅 설정
logging.basicConfig(
@@ -334,8 +335,8 @@ async def get_dashboard_data():
return ai_service.get_dashboard_data()
@app.post("/api/restart-models")
async def restart_models():
"""AI 모델 재시작 API"""
async def restart_models(api_key: str = Depends(get_api_key)):
"""AI 모델 재시작 API (보안)"""
try:
await ai_service.restart_models()
return {"message": "AI 모델 재시작이 완료되었습니다."}
@@ -343,8 +344,8 @@ async def restart_models():
raise HTTPException(status_code=500, detail=f"모델 재시작 실패: {str(e)}")
@app.post("/api/clear-cache")
async def clear_cache():
"""시스템 캐시 정리 API"""
async def clear_cache(api_key: str = Depends(get_api_key)):
"""시스템 캐시 정리 API (보안)"""
try:
# 여기서 실제 캐시 정리 로직 구현
# 예: 임시 파일 삭제, 메모리 정리 등

73
src/security.py Normal file
View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""
API 보안 및 인증 모듈
API 키 기반의 인증을 처리합니다.
"""
from fastapi import Security, HTTPException
from fastapi.security import APIKeyHeader
from starlette import status
from config_loader import settings
# 설정에서 API 키 가져오기
API_KEY = settings.get_section("security").get("api_key")
API_KEY_NAME = "X-API-KEY"
# API 키 헤더 정의
api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
async def get_api_key(header_value: str = Security(api_key_header)):
"""
요청 헤더에서 API 키를 추출하고 유효성을 검증합니다.
유효하지 않은 경우, HTTPException을 발생시킵니다.
"""
if not header_value:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="API 키가 필요합니다."
)
if header_value != API_KEY:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="제공된 API 키가 유효하지 않습니다."
)
return header_value
if __name__ == "__main__":
# 보안 모듈 테스트
print("✅ 보안 모듈 테스트")
print("-" * 30)
print(f" - 설정된 API 키 이름: {API_KEY_NAME}")
print(f" - 설정된 API 키 값: {API_KEY}")
# 예제: get_api_key 함수 테스트 (비동기 함수라 직접 실행은 어려움)
async def test_key_validation():
print("\n[테스트 시나리오]")
# 1. 유효한 키
try:
await get_api_key(API_KEY)
print(" - 유효한 키 검증: 성공 ✅")
except HTTPException as e:
print(f" - 유효한 키 검증: 실패 ❌ ({e.detail})")
# 2. 유효하지 않은 키
try:
await get_api_key("invalid-key")
print(" - 유효하지 않은 키 검증: 성공 ✅")
except HTTPException as e:
print(f" - 유효하지 않은 키 검증: 실패 ❌ ({e.detail})")
# 3. 키 없음
try:
await get_api_key(None)
print(" - 키 없음 검증: 성공 ✅")
except HTTPException as e:
print(f" - 키 없음 검증: 실패 ❌ ({e.detail})")
import asyncio
asyncio.run(test_key_validation())
print("-" * 30)