# JWT 토큰 검증 보안 개선 방안 작성일: 2025-08-15 작성자: 51123 서버, 51124 서버, 로컬 개발자 상태: 아이디어 → 추가 검토 필요 우선순위: 🔴 Critical (보안 취약점) 관련: auth-server, robeing services (rb8001, rb10408, rb10508), frontend ## 개요 JWT 토큰이 발급되지만 실제 API 호출 시 검증되지 않는 Critical 보안 취약점을 해결하기 위한 단계별 실행 계획입니다. 하이브리드 접근법(Phase 1: 최소 수정 → Phase 2: 프록시 패턴)을 통해 즉각적인 보안 개선과 장기적 아키텍처 개선을 동시에 추진합니다. ## 구현 방식 결정 ### 하이브리드 접근법 채택 - **Phase 1 (즉시)**: Option 1 - JWT_SECRET 공유로 빠른 구현 - **Phase 2 (1주 후)**: Option 2 - auth-server 프록시 패턴으로 전환 ## 기술적 결정 사항 ### 확정 사항 | 항목 | 결정 내용 | 근거 | |------|-----------|------| | JWT_SECRET | 새로 생성 (32자 이상) | 보안 강화 | | 토큰 만료 시간 | Access: 2시간 | 보안-편의성 균형 | | Refresh Token | Phase 2에서 구현 | 초기 복잡도 감소 | | 에러 응답 형식 | 표준화된 JSON | 일관성 | | X-User-Id 헤더 | 2주간 유지 후 제거 | 호환성 보장 | ### 에러 응답 표준 ```json { "error": "unauthorized", "message": "Invalid or expired token", "code": "AUTH_001", "status": 401 } ``` ## Phase 1: 최소 수정 (D-Day ~ D+2) ### Day 0: 환경 준비 및 Frontend 수정 #### 51123 서버 작업 ```bash # JWT_SECRET 생성 및 설정 openssl rand -base64 32 # 결과를 모든 서버의 .env에 동일하게 설정 ``` #### 로컬 개발자 작업 1. **robeing-api.ts 수정** ```typescript export async function sendMessage( text: string, token: string, userId: string ): Promise { const response = await fetch(`${ROBEING_API_URL}/api/chat`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, // JWT 토큰 추가 'X-User-Id': userId // 호환성 유지 (2주 후 제거) }, body: JSON.stringify({ message: text, user_id: userId }) }); return response.json(); } ``` 2. **chat-interface.tsx 수정** ```typescript import { useAuth } from '@/contexts/auth-context'; const ChatInterface = () => { const { user, isAuthenticated } = useAuth(); const handleSend = async (input: string) => { if (!isAuthenticated || !user) { // 로그인 페이지로 리다이렉트 return; } const token = localStorage.getItem('token'); // 'token' 키 확인됨 const response = await sendMessage(input, token, user.id); }; }; ``` ### Day 1: Backend JWT 검증 구현 #### 51124 서버 작업 1. **JWT 검증 미들웨어 생성** (각 로빙 서비스에 추가) ```python # app/core/auth.py from jose import jwt, JWTError from fastapi import HTTPException, Request import os JWT_SECRET = os.getenv("JWT_SECRET") JWT_ALGORITHM = "HS256" async def verify_jwt_token(request: Request): """JWT 토큰 검증 미들웨어""" # Phase 1: X-User-Id 폴백 지원 (2주간) x_user_id = request.headers.get("X-User-Id") # Authorization 헤더 확인 auth_header = request.headers.get("Authorization", "") if not auth_header.startswith("Bearer "): if x_user_id: # 임시 폴백 request.state.user_id = x_user_id return x_user_id raise HTTPException(status_code=401, detail="Token missing") token = auth_header.replace("Bearer ", "") try: payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM]) user_id = payload.get("sub", "default") request.state.user_id = user_id return user_id except JWTError as e: if x_user_id: # 임시 폴백 request.state.user_id = x_user_id return x_user_id raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}") ``` 2. **각 서비스에 미들웨어 적용** ```python # main.py 수정 from app.core.auth import verify_jwt_token @app.post("/api/chat") async def chat(request: Request): user_id = await verify_jwt_token(request) # 기존 로직 계속... ``` 3. **requirements.txt 업데이트** ``` python-jose[cryptography]==3.3.0 ``` ### Day 2: 통합 테스트 및 모니터링 #### 테스트 시나리오 1. **Critical Tests (필수)** - [x] 정상 토큰으로 요청 → 200 OK - [x] 잘못된 토큰으로 요청 → 401 Unauthorized - [x] 토큰 없이 요청 (X-User-Id만) → 200 OK (폴백) - [x] 만료된 토큰으로 요청 → 401 Unauthorized 2. **모니터링 설정** ```python # 로깅 추가 import logging logger = logging.getLogger(__name__) async def verify_jwt_token(request: Request): # ... 검증 로직 ... if auth_failed: logger.warning(f"Auth failed: IP={request.client.host}, User-Agent={request.headers.get('User-Agent')}") ``` ## Phase 2: 프록시 패턴 전환 (D+7) ### auth-server 프록시 구현 (51123) ```python # auth-server에 프록시 엔드포인트 추가 @app.api_route("/api/proxy/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) async def proxy_request( path: str, request: Request, user_id: str = Depends(verify_jwt) ): """모든 API 요청을 검증 후 전달""" headers = dict(request.headers) headers["X-Verified-User-Id"] = user_id headers.pop("Authorization", None) # 내부 통신에서는 제거 # 51124 서버로 전달 async with httpx.AsyncClient() as client: response = await client.request( method=request.method, url=f"http://192.168.219.52:8000/api/{path}", headers=headers, content=await request.body() ) return response.json() ``` ### Nginx 설정 변경 (51123) ```nginx # /etc/nginx/sites-available/ro-being location /api/ { proxy_pass http://localhost:9000/api/proxy/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } ``` ## 리스크 관리 ### 완화 전략 | 리스크 | 영향도 | 완화 방안 | 담당 | |--------|--------|-----------|------| | Frontend 배포 지연 | 높음 | X-User-Id 폴백 2주 유지 | 로컬 | | JWT_SECRET 노출 | 매우 높음 | .env 관리, .gitignore 확인 | 전체 | | 성능 저하 | 중간 | JWT 검증 결과 캐싱 (Phase 2) | 51124 | | 토큰 만료 UX | 중간 | Refresh Token (Phase 2) | 51123 | ### 롤백 계획 ```python # 환경변수로 JWT 검증 on/off JWT_VERIFICATION_ENABLED = os.getenv("JWT_VERIFICATION_ENABLED", "true") == "true" async def verify_jwt_token(request: Request): if not JWT_VERIFICATION_ENABLED: # 기존 방식으로 폴백 return request.headers.get("X-User-Id", "default") # ... JWT 검증 로직 ... ``` ## 성공 지표 ### Phase 1 (D+3 측정) - [ ] 인증 실패율 < 1% (정상 사용자) - [ ] 평균 응답시간 증가 < 10ms - [ ] 보안 취약점 해결 확인 ### Phase 2 (D+10 측정) - [ ] 프록시 경유 성공률 > 99.9% - [ ] 평균 지연시간 < 20ms - [ ] X-User-Id 헤더 제거 완료 ## 작업 추적 ### Phase 1 체크리스트 - [ ] JWT_SECRET 생성 및 배포 (51123) - [ ] Frontend Authorization 헤더 추가 (로컬) - [ ] rb8001 JWT 검증 추가 (51124) - [ ] rb10408 JWT 검증 추가 (51124) - [ ] rb10508 JWT 검증 추가 (51124) - [ ] 통합 테스트 완료 (전체) - [ ] 모니터링 대시보드 확인 (51123) ### Phase 2 체크리스트 - [ ] auth-server 프록시 엔드포인트 개발 (51123) - [ ] Nginx 설정 변경 (51123) - [ ] 내부 통신 보안 검증 (51124) - [ ] X-User-Id 헤더 제거 (로컬) - [ ] Refresh Token 구현 (51123) - [ ] 최종 보안 감사 (전체) ## 커뮤니케이션 계획 ### 일일 동기화 - 시간: 매일 오전 10시 - 채널: 개발 채팅방 - 내용: 진행 상황, 이슈, 다음 작업 ### 긴급 이슈 에스컬레이션 1. 보안 취약점 추가 발견 시 2. 서비스 장애 발생 시 3. 롤백 필요 판단 시 ## 문서화 요구사항 ### 완료 후 작성 1. API 명세 업데이트 (Authorization 헤더 필수) 2. 트러블슈팅 가이드 (인증 실패 디버깅) 3. 운영 가이드 (JWT_SECRET 관리) 4. 보안 감사 보고서 --- **"보안은 프로세스입니다. 한 번의 수정이 아닌 지속적인 개선이 필요합니다."** 이 계획을 통해 즉각적인 보안 개선과 장기적인 아키텍처 개선을 동시에 달성합니다.