feat(security): 관리자 API에 API 키 인증 적용
- 민감한 API(모델 재시작, 캐시 정리)를 보호하기 위해 API 키 기반 인증 추가 - 모듈을 생성하여 API 키 검증 로직 중앙화 - 에 API 키를 설정하여 관리 용이성 확보 - FastAPI의 를 사용하여 엔드포인트에 보안 규칙 적용 - CODING_CONVENTIONS.md에 API 키 사용법 및 cURL 예제 문서화
This commit is contained in:
@@ -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` 서비스를 통해 관리됩니다. 이를 통해 시스템 재부팅 시 자동 시작 및 예기치 않은 종료 시 자동 재시작이 보장됩니다.
|
||||
|
||||
@@ -38,5 +38,8 @@
|
||||
"static_hosting": "static-hosting",
|
||||
"metadata": "metadata",
|
||||
"local_work_path": "~/Scripts/nllb-translation-system"
|
||||
},
|
||||
"security": {
|
||||
"api_key": "nllb-secret-key-!@#$%"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
73
src/security.py
Normal 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)
|
||||
Reference in New Issue
Block a user