- 결정해야 할 사항이 많아 추가 검토 필요 - plans → ideas 폴더로 이동 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
8.4 KiB
8.4 KiB
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주간 유지 후 제거 | 호환성 보장 |
에러 응답 표준
{
"error": "unauthorized",
"message": "Invalid or expired token",
"code": "AUTH_001",
"status": 401
}
Phase 1: 최소 수정 (D-Day ~ D+2)
Day 0: 환경 준비 및 Frontend 수정
51123 서버 작업
# JWT_SECRET 생성 및 설정
openssl rand -base64 32
# 결과를 모든 서버의 .env에 동일하게 설정
로컬 개발자 작업
- robing-api.ts 수정
export async function sendMessage(
text: string,
token: string,
userId: string
): Promise<MessageResponse> {
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();
}
- chat-interface.tsx 수정
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 서버 작업
- JWT 검증 미들웨어 생성 (각 로빙 서비스에 추가)
# 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)}")
- 각 서비스에 미들웨어 적용
# 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)
# 기존 로직 계속...
- requirements.txt 업데이트
python-jose[cryptography]==3.3.0
Day 2: 통합 테스트 및 모니터링
테스트 시나리오
-
Critical Tests (필수)
- 정상 토큰으로 요청 → 200 OK
- 잘못된 토큰으로 요청 → 401 Unauthorized
- 토큰 없이 요청 (X-User-Id만) → 200 OK (폴백)
- 만료된 토큰으로 요청 → 401 Unauthorized
-
모니터링 설정
# 로깅 추가
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)
# 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)
# /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 |
롤백 계획
# 환경변수로 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시
- 채널: 개발 채팅방
- 내용: 진행 상황, 이슈, 다음 작업
긴급 이슈 에스컬레이션
- 보안 취약점 추가 발견 시
- 서비스 장애 발생 시
- 롤백 필요 판단 시
문서화 요구사항
완료 후 작성
- API 명세 업데이트 (Authorization 헤더 필수)
- 트러블슈팅 가이드 (인증 실패 디버깅)
- 운영 가이드 (JWT_SECRET 관리)
- 보안 감사 보고서
"보안은 프로세스입니다. 한 번의 수정이 아닌 지속적인 개선이 필요합니다."
이 계획을 통해 즉각적인 보안 개선과 장기적인 아키텍처 개선을 동시에 달성합니다.