From f4eb534da41a3706aaee88510def2065aba0c18a Mon Sep 17 00:00:00 2001 From: happybell80 Date: Mon, 17 Nov 2025 19:46:18 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20Gateway=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=EC=9B=90=EC=B9=99=20=EC=A4=80=EC=88=98=20-=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B8=94=EB=A1=9D=20=EC=B5=9C=EC=86=8C=ED=99=94,?= =?UTF-8?q?=20100=EC=A4=84=20=EC=9D=B4=ED=95=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../251117_gateway_admin_routing_jwt_fix.md | 159 +++--------------- 1 file changed, 28 insertions(+), 131 deletions(-) diff --git a/journey/troubleshooting/251117_gateway_admin_routing_jwt_fix.md b/journey/troubleshooting/251117_gateway_admin_routing_jwt_fix.md index 65d08e0..0e48fbf 100644 --- a/journey/troubleshooting/251117_gateway_admin_routing_jwt_fix.md +++ b/journey/troubleshooting/251117_gateway_admin_routing_jwt_fix.md @@ -2,7 +2,12 @@ **날짜**: 2025-11-17 **작성자**: admin -**관련 이슈**: Admin Dashboard 표준 배포 방식 전환 +**관련 파일**: +- `robeing-gateway/app/routers/admin.py` +- `robeing-gateway/app/main.py` +- `robeing-gateway/docker-compose.yml` + +--- ## 문제 상황 @@ -12,7 +17,7 @@ Admin Dashboard를 표준 배포 방식으로 전환하면서 Gateway를 통한 2. 로그인 후 JWT 토큰 검증 실패 (401 Unauthorized) 3. Docker 컨테이너 간 통신 문제 -## 해결 과정 +## 해결 방안 ### 1. Gateway Admin Router 등록 @@ -22,75 +27,13 @@ Admin Dashboard를 표준 배포 방식으로 전환하면서 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" - ) -``` +**해결**: +- `robeing-gateway/app/main.py:92`: `app.include_router(admin.router, prefix="")` 추가 +- `robeing-gateway/app/routers/admin.py:17`: `router = APIRouter()` (prefix 없음) +- `robeing-gateway/app/routers/admin.py:22`: `/admin/api/{path:path}` 라우트 정의 **검증**: ```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}'] @@ -104,25 +47,12 @@ sudo docker exec robeing-gateway python -c \ - 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" -``` +**해결**: +- `robeing-gateway/app/routers/admin.py:20`: `ADMIN_BACKEND_URL = "http://admin-dashboard-backend:8000"` (localhost → 컨테이너 이름) **교훈**: - Docker 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용 - 같은 Docker 네트워크(`appnet`)에 있는 컨테이너는 이름으로 자동 DNS 해석됨 -- 컨테이너 이름은 `docker-compose.yml`의 `container_name` 또는 서비스 이름 사용 ### 3. JWT Secret Key 불일치 문제 @@ -131,7 +61,7 @@ sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend" **원인**: - 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"` +- Backend: `admin-dashboard/backend/admin_routes.py:32`의 `SECRET_KEY = "admin_secret_key_robeing_2025"` **에러 로그**: ``` @@ -139,38 +69,17 @@ sudo docker network inspect appnet | grep -A 5 "admin-dashboard-backend" {"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 -``` +**해결**: +- `robeing-gateway/docker-compose.yml:8`: `JWT_SECRET_KEY=admin_secret_key_robeing_2025` 환경변수 추가 +- Gateway 재빌드: `docker compose down && 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,...},...} @@ -179,35 +88,23 @@ curl -X GET http://localhost:8100/admin/api/system/overview \ **교훈**: - JWT 검증을 위해서는 발급 서버와 검증 서버의 secret key가 반드시 일치해야 함 - Docker Compose의 `environment` 섹션으로 `.env` 파일의 환경변수 오버라이드 가능 -- 운영 환경에서는 환경변수로 관리하여 보안 강화 필요 ## 최종 구조 ``` -[Browser] - → [Nginx] /admin/api/* - → [Gateway:8100] /admin/api/{path} - → [Admin Backend:8000] /admin/{path} +[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: 데이터 반환 +## 교훈 -## 관련 파일 +### Docker 네트워크 통신 +- 컨테이너 간 통신은 `localhost`가 아닌 컨테이너 이름 사용 +- 같은 네트워크의 컨테이너는 이름으로 자동 DNS 해석 -- `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) 확인 필요 +### JWT 검증 +- 발급 서버와 검증 서버의 secret key 반드시 일치 필요 +- 환경변수 오버라이드를 통한 설정 통일 가능 +### Gateway 라우팅 +- Router 등록 시 prefix 명시 필요 +- API 경로는 `/admin/api/*` 형식으로 통일