From c4253e066e3f2b339f69b350b22a029863c6093f Mon Sep 17 00:00:00 2001 From: happybell80 Date: Fri, 15 Aug 2025 14:36:58 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20JWT=20=ED=86=A0=ED=81=B0=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=B3=B4=EC=95=88=20=EA=B0=9C=EC=84=A0=20-=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=EC=96=B4=EB=A5=BC=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EA=B3=84=ED=9A=8D=EC=9C=BC=EB=A1=9C=20=EC=8A=B9?= =?UTF-8?q?=EA=B2=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 하이브리드 접근법 채택: Phase 1(최소 수정) → Phase 2(프록시 패턴) - 51123, 51124, 로컬 개발자 간 합의 사항 반영 - JWT_SECRET 새로 생성, 토큰 만료 2시간 결정 - X-User-Id 헤더 2주간 폴백 지원으로 호환성 보장 - Day별 구체적 실행 계획 및 코드 예시 포함 - 롤백 계획 및 성공 지표 명시 Critical 보안 취약점 해결을 위한 즉시 실행 가능한 계획 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- ..._시스템_보안_개선_필요사항.md | 323 ------------------ ...큰_검증_보안_개선_실행계획.md | 293 ++++++++++++++++ 2 files changed, 293 insertions(+), 323 deletions(-) delete mode 100644 ideas/250815_로그인_인증_시스템_보안_개선_필요사항.md create mode 100644 plans/250815_JWT_토큰_검증_보안_개선_실행계획.md diff --git a/ideas/250815_로그인_인증_시스템_보안_개선_필요사항.md b/ideas/250815_로그인_인증_시스템_보안_개선_필요사항.md deleted file mode 100644 index 542563a..0000000 --- a/ideas/250815_로그인_인증_시스템_보안_개선_필요사항.md +++ /dev/null @@ -1,323 +0,0 @@ -# 로그인 인증 시스템 보안 개선 필요사항 - -작성일: 2025-08-15 -작성자: Claude & happybell80 -상태: 아이디어 → 긴급 개선 필요 -관련: auth-server, robing-gateway, frontend - -## 개요 - -현재 로빙 프로젝트의 인증 시스템에서 JWT 토큰은 발급되지만 실제 API 호출 시 검증되지 않는 심각한 보안 취약점이 발견되었습니다. 누구나 X-User-Id 헤더를 조작하여 다른 사용자로 위장할 수 있는 상태입니다. - -## 현재 인증 흐름 - -``` -1. Frontend → auth-server로 리다이렉트 (/auth/gmail/login) - ↓ -2. Auth-server → Google OAuth → 콜백 받음 - ↓ -3. Auth-server → Redis에 임시 코드 저장 → Frontend로 리다이렉트 (#code=xxx) - ↓ -4. Frontend → 코드로 토큰 교환 (/auth/verify) → JWT 토큰 받음 - ↓ -5. Frontend → localStorage에 토큰 저장 - ↓ -6. Frontend → robing-gateway 호출 시 X-User-Id 헤더만 전송 ⚠️ - ↓ -7. robing-gateway → 토큰 검증 없이 X-User-Id 신뢰 ⚠️ -``` - -## 발견된 보안 취약점 - -### 1. 인증 토큰 미전달 -- **현상**: Frontend가 JWT 토큰을 저장하지만 API 호출 시 전송하지 않음 -- **코드 위치**: frontend의 API 클라이언트 -- **영향**: 인증 메커니즘이 무의미함 - -### 2. 토큰 검증 부재 -- **현상**: robing-gateway가 X-User-Id 헤더만 확인하고 JWT 검증 없음 -- **코드 예시**: - ```python - # 현재 (취약함) - user_id = request.headers.get("X-User-Id", "default") - - # 필요한 것 - token = request.headers.get("Authorization", "").replace("Bearer ", "") - user_id = verify_jwt_token(token) # 검증 필요! - ``` -- **영향**: 누구나 헤더 조작으로 다른 사용자 행세 가능 - -### 3. 시스템 간 연결 끊김 -- **auth-server**: JWT 발급 기능 있음 (51123 서버, 포트 9000) -- **robing-gateway**: JWT 검증 기능 없음 (51124 서버) -- **frontend**: 토큰 보유하지만 전송 안 함 -- **영향**: 인증 시스템이 형식적으로만 존재 - -### 4. 기본값 폴백 문제 -- **현상**: X-User-Id 없으면 "default"로 처리 -- **영향**: 모든 미인증 사용자가 동일한 로빙/데이터 공유 - -## 긴급 수정 필요사항 - -### Frontend 수정 - -### robing-api.ts 수정 필요 -```typescript -// 현재 문제: 토큰 전송 안 함, 하드코딩된 userId -export async function sendMessage(text: string, userId: string = 'test_user'): Promise { - const response = await fetch(`${ROBING_API_URL}/api/chat`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-User-Id': userId // ❌ 토큰 없음, 조작 가능 - }, - body: JSON.stringify({ - message: text, - user_id: userId - }) - }); -} - -// 수정 필요 -export async function sendMessage(text: string, token: string, userId: string): Promise { - const response = await fetch(`${ROBING_API_URL}/api/chat`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, // ✅ JWT 토큰 추가 - 'X-User-Id': userId // 호환성 유지 (임시) - }, - body: JSON.stringify({ - message: text, - user_id: userId - }) - }); -} -``` - -### chat-interface.tsx 수정 필요 -```typescript -// 현재: useAuth 사용 안 함 -import { sendMessage } from '@/services/robing-api'; - -// 수정 필요 -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('auth_token'); - const response = await sendMessage(input, token, user.id); - }; -}; -``` - -### robing-gateway 수정 -```python -# JWT 검증 미들웨어 추가 -from jose import jwt, JWTError - -async def verify_token(request: Request): - token = request.headers.get("Authorization", "").replace("Bearer ", "") - if not token: - raise HTTPException(status_code=401, detail="Token missing") - - try: - payload = jwt.decode(token, JWT_SECRET, algorithms=["HS256"]) - request.state.user_id = payload.get("sub", "default") - except JWTError: - raise HTTPException(status_code=401, detail="Invalid token") - - return request.state.user_id - -@app.post("/api/chat") -async def chat(request: Request): - user_id = await verify_token(request) # 검증 필수! - # ... -``` - -### 시스템 간 연동 -1. **JWT 시크릿 공유**: auth-server와 robing-gateway가 동일한 JWT_SECRET 사용 -2. **환경변수 설정**: - ```bash - # .env 파일 - JWT_SECRET=동일한_시크릿_키_사용 - JWT_ALGORITHM=HS256 - JWT_EXPIRY_HOURS=24 - ``` - -### 에러 처리 -```python -# 인증 실패 시 명확한 에러 -@app.exception_handler(401) -async def unauthorized_handler(request, exc): - return JSONResponse( - status_code=401, - content={"error": "Authentication required", "detail": str(exc)} - ) -``` - -## 추가 개선사항 - -### 1. Refresh Token 구현 -```python -# 장기 세션 유지를 위한 refresh token -def create_tokens(user_id: str): - access_token = create_jwt(user_id, expires_in=timedelta(hours=1)) - refresh_token = create_jwt(user_id, expires_in=timedelta(days=30)) - return access_token, refresh_token -``` - -### 2. 토큰 블랙리스트 -```python -# Redis를 활용한 로그아웃 토큰 관리 -async def logout(token: str): - await redis.setex(f"blacklist:{token}", JWT_EXPIRY_HOURS * 3600, "1") - -async def is_token_blacklisted(token: str): - return await redis.exists(f"blacklist:{token}") -``` - -### 3. Role-Based Access Control (RBAC) -```python -# JWT에 역할 정보 포함 -payload = { - "sub": user_id, - "email": user_email, - "role": "admin", # or "user", "guest" - "permissions": ["read", "write", "delete"] -} -``` - -## 서버별 현황 및 설정 관리 문제 - -### 51123 서버 -- **auth-server** 실행 중 (포트 9000) -- JWT 발급 기능 정상 -- Redis 임시 코드 시스템 작동 -- ✅ pydantic BaseSettings 사용 - -### 51124 서버 -- **robing-gateway** 실행 중 -- JWT 검증 로직 없음 ⚠️ -- 모든 로빙 서비스 운영 -- ❌ **config.py 없음** - os.getenv() 분산 사용 (database.py에서 직접 호출) - -### 로컬 개발 -- **frontend** 개발 -- 토큰 저장은 하지만 전송 안 함 ⚠️ -- API URL 문제: `https://ro-being.com/rb10508` 대신 Gateway URL 사용 필요 - -### 설정 관리 표준화 필요 -| 서비스 | Config 위치 | 상태 | -|--------|------------|------| -| rb10508_micro | app/config.py | ✅ 표준 | -| rb8001 | app/core/config.py | ✅ 표준 | -| skill-slack | app/core/config.py | ✅ 표준 | -| robing-gateway | **없음** | ❌ 비표준 | - -robing-gateway도 BaseSettings 패턴으로 전환 필요 - -## 보안 위험 수준 - -**🔴 Critical (긴급)** - -현재 상태에서는: -1. 누구나 다른 사용자로 위장 가능 -2. 개인 데이터 접근 가능 -3. 로빙 설정 변경 가능 -4. 실질적인 인증 보호 없음 - -## 서버팀 관점 분석 - -### 🚨 현재 상황: 치명적 보안 취약점 - -인증 시스템이 껍데기만 있고 실제로는 작동하지 않는 상태입니다. - -### 핵심 문제 요약 - -1. **토큰은 있는데 쓰지 않음** - - Frontend: JWT 받아서 localStorage에 저장만 함 - - API 호출 시: Authorization 헤더 없이 X-User-Id만 전송 - -2. **검증 없이 믿음** - - robing-gateway: X-User-Id 헤더만 보고 "아, 이 사람이구나" 믿음 - - 마치 신분증 검사 없이 "저 김철수입니다" 하면 믿는 것 - -3. **시스템 단절** - - 51123: auth-server (JWT 발급) - - 51124: robing-gateway (JWT 검증 코드 없음) - - 서로 JWT_SECRET도 공유 안 함 - -### 실제 해킹 시연 - -누구나 5초면 해킹 가능: -```bash -curl -X POST https://ro-being.com/api/chat \ - -H "X-User-Id: happybell80" \ - -d '{"message": "비밀번호 알려줘"}' -``` - -### 즉시 필요한 조치 (24시간 내) - -1. robing-gateway에 JWT 검증 미들웨어 추가 -2. Frontend API 클라이언트에 Authorization 헤더 추가 -3. JWT_SECRET 환경변수 통일 - -### 기술적 판단 -- **난이도**: 낮음 (코드 10-20줄) -- **영향도**: 극대 -- **작업 시간**: 2-3시간 - -### 의문점 -1. 왜 처음부터 이렇게 만들었을까? -2. 테스트 중이라 일부러 풀어놓은 건가? -3. 아니면 단순 실수? - -### 결론 - -> "자물쇠는 있는데 열쇠 구멍이 막혀있는 상태" - -지금 당장 고치지 않으면 모든 사용자 데이터가 위험합니다. 51124 서버만으로는 수정 불가능하고, 23 서버와 로컬 개발자의 즉각적인 협업이 필요합니다. - -## 구현 우선순위 - -1. **즉시 (1일 내)** - - robing-gateway에 JWT 검증 추가 - - Frontend API 호출 시 토큰 헤더 추가 - -2. **긴급 (1주 내)** - - JWT 시크릿 환경변수 통일 - - 에러 처리 및 로깅 추가 - -3. **중요 (2주 내)** - - Refresh token 구현 - - 토큰 블랙리스트 관리 - - RBAC 권한 시스템 - -## 테스트 체크리스트 - -- [ ] Frontend에서 Authorization 헤더 전송 확인 -- [ ] robing-gateway JWT 검증 동작 확인 -- [ ] 잘못된 토큰 시 401 에러 반환 -- [ ] 토큰 없을 시 적절한 에러 메시지 -- [ ] 토큰 만료 시 재로그인 유도 -- [ ] 로그아웃 시 토큰 무효화 - -## 참고사항 - -- JWT 라이브러리: python-jose (이미 사용 중) -- Redis: 임시 코드 및 블랙리스트 관리 -- 환경변수: JWT_SECRET 반드시 동일하게 설정 - ---- - -**"인증은 시스템의 문지기입니다. 문이 열려있다면 의미가 없습니다."** - -이 문제는 즉시 수정이 필요한 보안 취약점입니다. \ No newline at end of file diff --git a/plans/250815_JWT_토큰_검증_보안_개선_실행계획.md b/plans/250815_JWT_토큰_검증_보안_개선_실행계획.md new file mode 100644 index 0000000..a066168 --- /dev/null +++ b/plans/250815_JWT_토큰_검증_보안_개선_실행계획.md @@ -0,0 +1,293 @@ +# JWT 토큰 검증 보안 개선 실행 계획 + +작성일: 2025-08-15 +작성자: 51123 서버, 51124 서버, 로컬 개발자 +상태: 계획 확정 → 실행 중 +우선순위: 🔴 Critical (즉시 실행) +관련: auth-server, robing 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. **robing-api.ts 수정** +```typescript +export async function sendMessage( + text: string, + token: string, + userId: string +): Promise { + const response = await fetch(`${ROBING_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. 보안 감사 보고서 + +--- + +**"보안은 프로세스입니다. 한 번의 수정이 아닌 지속적인 개선이 필요합니다."** + +이 계획을 통해 즉각적인 보안 개선과 장기적인 아키텍처 개선을 동시에 달성합니다. \ No newline at end of file