# Gateway Admin API 라우팅 및 JWT 검증 문제 해결 **날짜**: 2025-11-17 **작성자**: admin **관련 이슈**: Admin Dashboard 표준 배포 방식 전환 ## 문제 상황 Admin Dashboard를 표준 배포 방식으로 전환하면서 Gateway를 통한 API 프록시가 필요했으나, 다음 문제들이 발생: 1. Gateway에서 admin API 요청이 404 오류 발생 2. 로그인 후 JWT 토큰 검증 실패 (401 Unauthorized) 3. Docker 컨테이너 간 통신 문제 ## 해결 과정 ### 1. Gateway Admin Router 등록 **문제**: `/admin/api/*` 요청이 Gateway에서 처리되지 않음 **원인**: - `robeing-gateway/app/routers/admin.py`의 router가 제대로 등록되지 않음 - router prefix 설정 문제 **해결** (`robeing-gateway/app/main.py`): ```python # Include routers app.include_router(slack.router) app.include_router(admin.router, prefix="") # prefix 명시 ``` **라우터 정의** (`robeing-gateway/app/routers/admin.py`): ```python from fastapi import APIRouter, Request, HTTPException, Header from typing import Optional import httpx import logging from app.core.auth import get_verified_user logger = logging.getLogger(__name__) router = APIRouter() ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000" @router.api_route("/admin/api/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"]) async def admin_api_proxy( request: Request, path: str, authorization: Optional[str] = Header(None) ): """ Admin API 프록시 - 로그인은 JWT 검증 생략, 나머지는 필수 """ # 로그인 API는 JWT 검증 생략 if path == "login": logger.info(f"Admin login request to /admin/api/{path}") else: # JWT 검증 (필수) try: user_uuid = get_verified_user(authorization) logger.info(f"Admin API request from user {user_uuid} to /admin/api/{path}") except HTTPException as e: logger.warning(f"Admin API request without valid JWT to /admin/api/{path}") raise e # Backend로 프록시 target_url = f"{ADMIN_BACKEND_URL}/admin/{path}" async with httpx.AsyncClient(timeout=30.0) as client: try: response = await client.request( method=request.method, url=target_url, headers=dict(request.headers), content=await request.body() ) return Response( content=response.content, status_code=response.status_code, headers=dict(response.headers) ) except httpx.RequestError as e: logger.error(f"Failed to proxy admin API request: {e}") raise HTTPException( status_code=503, detail="Admin backend service unavailable" ) ``` **검증**: ```bash # Gateway 라우트 확인 sudo docker exec robeing-gateway python -c \ "from app.main import app; print([r.path for r in app.routes if 'admin' in r.path])" # ['/admin/api/{path:path}'] ``` ### 2. Docker 네트워크 내부 통신 문제 **문제**: Gateway 컨테이너에서 `localhost:8000`으로 접근 시 자기 자신을 가리킴 **원인**: - Docker 컨테이너 내부에서 `localhost`는 컨테이너 자신을 의미 - 다른 컨테이너에 접근하려면 컨테이너 이름 또는 네트워크 IP 사용 필요 **해결** (`robeing-gateway/app/routers/admin.py`): ```python # Before: ADMIN_BACKEND_URL = "http://localhost:8000" # After: ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000" ``` **Docker 네트워크 확인**: ```bash # 같은 네트워크에 있는 컨테이너 확인 sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend" # "Name": "admin-dashboard-backend", # "IPv4Address": "172.21.0.4/16" ``` **교훈**: - Docker 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용 - 같은 Docker 네트워크(`appnet`)에 있는 컨테이너는 이름으로 자동 DNS 해석됨 - 컨테이너 이름은 `docker-compose.yml`의 `container_name` 또는 서비스 이름 사용 ### 3. JWT Secret Key 불일치 문제 **문제**: 로그인은 성공하지만 이후 API 호출 시 401 Unauthorized 오류 **원인**: - Gateway의 JWT_SECRET_KEY와 admin-dashboard backend의 SECRET_KEY가 불일치 - Gateway: `.env` 파일의 `JWT_SECRET_KEY=9cc562b6296b87b02dd89045a2e7e11c249713a59a5ac0160d852121f1289664` - Backend: `admin_routes.py`의 `SECRET_KEY = "admin_secret_key_robeing_2025"` **에러 로그**: ``` 2025-11-17 19:36:23,945 - app.core.auth - ERROR - JWT verification failed: Not enough segments {"detail":"Token validation failed: Signature verification failed."} ``` **해결** (`robeing-gateway/docker-compose.yml`): ```yaml services: robeing-gateway: container_name: robeing-gateway build: . env_file: - .env environment: - JWT_SECRET_KEY=admin_secret_key_robeing_2025 # Backend와 일치 ports: - "${CNTR_PORT:-8100}:8000" networks: - appnet ``` **재빌드**: ```bash cd /home/admin/robeing-gateway sudo docker compose down sudo docker compose up -d --build ``` **검증**: ```bash # JWT 토큰 발급 TOKEN=$(curl -s -X POST http://localhost:8000/admin/login \ -H "Content-Type: application/json" \ -d '{"password":"19800508"}' | python3 -c \ "import sys, json; print(json.load(sys.stdin)['access_token'])") # Gateway를 통한 API 호출 테스트 curl -X GET http://localhost:8100/admin/api/system/overview \ -H "Authorization: Bearer $TOKEN" # {"timestamp":"2025-11-17T10:37:21.154596","cpu":{"percent":4.6,...},...} ``` **교훈**: - JWT 검증을 위해서는 발급 서버와 검증 서버의 secret key가 반드시 일치해야 함 - Docker Compose의 `environment` 섹션으로 `.env` 파일의 환경변수 오버라이드 가능 - 운영 환경에서는 환경변수로 관리하여 보안 강화 필요 ## 최종 구조 ``` [Browser] → [Nginx] /admin/api/* → [Gateway:8100] /admin/api/{path} → [Admin Backend:8000] /admin/{path} ``` **요청 흐름**: 1. 브라우저: `POST /admin/api/login` (비밀번호 전송) 2. Nginx: Gateway로 프록시 (`http://localhost:8100/admin/api/login`) 3. Gateway: 로그인은 JWT 검증 생략, Backend로 프록시 (`http://admin-dashboard-backend:8000/admin/login`) 4. Backend: JWT 토큰 발급 후 반환 5. 브라우저: 토큰 저장 후 `GET /admin/api/system/overview` (Authorization 헤더 포함) 6. Gateway: JWT 검증 후 Backend로 프록시 7. Backend: 데이터 반환 ## 관련 파일 - `robeing-gateway/app/routers/admin.py`: Admin API 프록시 라우터 - `robeing-gateway/app/main.py`: Router 등록 - `robeing-gateway/docker-compose.yml`: JWT_SECRET_KEY 환경변수 설정 - `admin-dashboard/backend/admin_routes.py`: JWT 토큰 발급 (SECRET_KEY) ## 참고 - [Admin Dashboard 표준 배포 전환 문서](./251117_admin_dashboard_standard_deployment_refactoring.md) - Gateway는 nginx를 통해 프록시되므로 nginx 설정(`/admin/api/` → Gateway) 확인 필요