# 보안 가이드 (SECURITY.md) ## 🔐 보안 철학 Todo-Project는 **개인용 도구**로 설계되어 **편의성**과 **적절한 보안** 사이의 균형을 추구합니다. ### 보안 원칙 - **Trust but Verify**: 신뢰할 수 있는 기기에서는 간편하게, 의심스러운 접근은 차단 - **최소 권한**: 필요한 최소한의 권한만 부여 - **개인 최적화**: 개인 사용에 최적화된 보안 모델 ## 🛡️ 보안 레벨 ### 1. Minimal (개인용 권장) ```python SECURITY_MINIMAL = { "device_remember_days": 30, # 30일간 기기 기억 "require_password": False, # 기기 등록 후 비밀번호 불필요 "session_timeout": 0, # 무제한 세션 "biometric_optional": True, # 생체 인증 선택사항 "auto_login": True # 자동 로그인 활성화 } ``` **적합한 환경**: 개인 기기 (내 폰, 내 컴퓨터)에서만 사용 ### 2. Balanced (일반 권장) ```python SECURITY_BALANCED = { "device_remember_days": 7, # 7일간 기기 기억 "require_password": True, # 주기적 비밀번호 확인 "session_timeout": 24*60, # 24시간 세션 "biometric_optional": True, # 생체 인증 선택사항 "auto_login": False # 수동 로그인 } ``` **적합한 환경**: 가끔 다른 기기에서도 접근하는 경우 ### 3. Secure (높은 보안) ```python SECURITY_SECURE = { "device_remember_days": 1, # 1일간만 기기 기억 "require_password": True, # 매번 비밀번호 확인 "session_timeout": 60, # 1시간 세션 "biometric_required": True, # 생체 인증 필수 "auto_login": False # 수동 로그인 } ``` **적합한 환경**: 민감한 정보가 포함된 경우 ## 🔑 인증 시스템 ### 기기 등록 방식 #### 기기 식별 ```python class DeviceFingerprint: """기기 고유 식별자 생성""" def generate_fingerprint(self, request): """브라우저 fingerprint 생성""" components = [ request.headers.get('User-Agent', ''), request.headers.get('Accept-Language', ''), request.headers.get('Accept-Encoding', ''), self.get_screen_resolution(), # JavaScript에서 전송 self.get_timezone(), # JavaScript에서 전송 self.get_platform_info() # JavaScript에서 전송 ] fingerprint = hashlib.sha256( '|'.join(components).encode('utf-8') ).hexdigest() return fingerprint[:16] # 16자리 축약 ``` #### 기기 등록 프로세스 ```python class DeviceRegistration: """기기 등록 관리""" async def register_device(self, user_id, device_info, user_confirmation): """새 기기 등록""" # 1. 사용자 확인 (비밀번호 또는 기존 기기에서 승인) if not await self.verify_user_identity(user_id, user_confirmation): raise AuthenticationError("사용자 확인 실패") # 2. 기기 정보 생성 device_id = self.generate_device_id(device_info) device_name = device_info.get('name', '알 수 없는 기기') # 3. 장기 토큰 생성 (30일 유효) device_token = self.create_device_token(user_id, device_id) # 4. 기기 정보 저장 device_record = { "device_id": device_id, "user_id": user_id, "device_name": device_name, "fingerprint": device_info['fingerprint'], "registered_at": datetime.now(), "last_used": datetime.now(), "token": device_token, "expires_at": datetime.now() + timedelta(days=30), "is_trusted": True } await self.save_device_record(device_record) return device_token ``` ### 토큰 관리 #### JWT 토큰 구조 ```python class TokenManager: """토큰 생성 및 관리""" def create_device_token(self, user_id, device_id): """장기간 유효한 기기 토큰 생성""" payload = { "user_id": str(user_id), "device_id": device_id, "token_type": "device", "issued_at": datetime.utcnow(), "expires_at": datetime.utcnow() + timedelta(days=30) } return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") def create_session_token(self, user_id, device_id): """세션 토큰 생성""" payload = { "user_id": str(user_id), "device_id": device_id, "token_type": "session", "issued_at": datetime.utcnow(), "expires_at": datetime.utcnow() + timedelta(hours=24) } return jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") ``` #### 토큰 검증 ```python class TokenValidator: """토큰 검증""" async def validate_device_token(self, token): """기기 토큰 검증""" try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) # 토큰 타입 확인 if payload.get("token_type") != "device": return None # 만료 시간 확인 expires_at = datetime.fromisoformat(payload["expires_at"]) if datetime.utcnow() > expires_at: return None # 기기 정보 확인 device_record = await self.get_device_record( payload["user_id"], payload["device_id"] ) if not device_record or not device_record["is_trusted"]: return None return payload except jwt.JWTError: return None ``` ## 🔒 데이터 보안 ### 데이터베이스 보안 #### 비밀번호 해싱 ```python from passlib.context import CryptContext pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") def hash_password(password: str) -> str: """비밀번호 해싱 (bcrypt)""" return pwd_context.hash(password) def verify_password(plain_password: str, hashed_password: str) -> bool: """비밀번호 검증""" return pwd_context.verify(plain_password, hashed_password) ``` #### 민감 정보 암호화 ```python from cryptography.fernet import Fernet class DataEncryption: """민감 정보 암호화""" def __init__(self): self.key = settings.ENCRYPTION_KEY.encode() self.cipher = Fernet(self.key) def encrypt_sensitive_data(self, data: str) -> str: """민감 정보 암호화 (시놀로지 비밀번호 등)""" return self.cipher.encrypt(data.encode()).decode() def decrypt_sensitive_data(self, encrypted_data: str) -> str: """민감 정보 복호화""" return self.cipher.decrypt(encrypted_data.encode()).decode() ``` ### 네트워크 보안 #### HTTPS 강제 ```python from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware # 프로덕션에서 HTTPS 강제 if not settings.DEBUG: app.add_middleware(HTTPSRedirectMiddleware) ``` #### CORS 설정 ```python from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=settings.ALLOWED_ORIGINS, # 특정 도메인만 허용 allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["*"], ) ``` ## 🚨 보안 모니터링 ### 로그인 시도 모니터링 ```python class SecurityMonitor: """보안 모니터링""" def __init__(self): self.failed_attempts = {} # IP별 실패 횟수 self.blocked_ips = set() # 차단된 IP async def record_login_attempt(self, ip_address, success): """로그인 시도 기록""" if success: # 성공 시 실패 횟수 초기화 self.failed_attempts.pop(ip_address, None) else: # 실패 시 횟수 증가 self.failed_attempts[ip_address] = \ self.failed_attempts.get(ip_address, 0) + 1 # 5회 실패 시 30분 차단 if self.failed_attempts[ip_address] >= 5: self.block_ip(ip_address, minutes=30) def block_ip(self, ip_address, minutes=30): """IP 주소 차단""" self.blocked_ips.add(ip_address) # 일정 시간 후 차단 해제 asyncio.create_task( self.unblock_ip_after(ip_address, minutes) ) ``` ### 의심스러운 활동 감지 ```python class AnomalyDetection: """이상 활동 감지""" async def detect_suspicious_activity(self, user_id, activity): """의심스러운 활동 감지""" # 1. 비정상적인 시간대 접근 if self.is_unusual_time(activity.timestamp): await self.alert_unusual_time_access(user_id, activity) # 2. 새로운 기기에서 접근 if not await self.is_known_device(user_id, activity.device_info): await self.alert_new_device_access(user_id, activity) # 3. 비정상적인 API 호출 패턴 if await self.is_unusual_api_pattern(user_id, activity): await self.alert_unusual_api_pattern(user_id, activity) ``` ## 🔧 보안 설정 ### 환경 변수 보안 ```bash # .env 파일 보안 설정 SECRET_KEY=your-very-long-and-random-secret-key-here ENCRYPTION_KEY=your-32-byte-encryption-key-here # 시놀로지 인증 정보 (암호화 저장) SYNOLOGY_USERNAME=encrypted_username SYNOLOGY_PASSWORD=encrypted_password # 보안 레벨 설정 SECURITY_LEVEL=minimal # minimal, balanced, secure ENABLE_DEVICE_REGISTRATION=true ENABLE_BIOMETRIC_AUTH=true ENABLE_SECURITY_MONITORING=true # 세션 설정 SESSION_TIMEOUT_MINUTES=1440 # 24시간 DEVICE_REMEMBER_DAYS=30 ``` ### 보안 헤더 ```python from fastapi.middleware.trustedhost import TrustedHostMiddleware from fastapi.responses import Response # 신뢰할 수 있는 호스트만 허용 app.add_middleware( TrustedHostMiddleware, allowed_hosts=["localhost", "127.0.0.1", "your-domain.com"] ) @app.middleware("http") async def add_security_headers(request, call_next): """보안 헤더 추가""" response = await call_next(request) response.headers["X-Content-Type-Options"] = "nosniff" response.headers["X-Frame-Options"] = "DENY" response.headers["X-XSS-Protection"] = "1; mode=block" response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains" return response ``` ## 🛠️ 보안 체크리스트 ### 개발 환경 - [ ] `.env` 파일이 `.gitignore`에 포함되어 있는가? - [ ] 기본 비밀번호가 변경되었는가? - [ ] 디버그 모드가 비활성화되어 있는가? (프로덕션) - [ ] 로그에 민감 정보가 포함되지 않는가? ### 인증 시스템 - [ ] 비밀번호가 안전하게 해싱되어 있는가? - [ ] JWT 토큰에 민감 정보가 포함되지 않는가? - [ ] 토큰 만료 시간이 적절하게 설정되어 있는가? - [ ] 기기 등록 프로세스가 안전한가? ### 네트워크 보안 - [ ] HTTPS가 활성화되어 있는가? (프로덕션) - [ ] CORS 설정이 적절한가? - [ ] 보안 헤더가 설정되어 있는가? - [ ] 불필요한 포트가 차단되어 있는가? ### 데이터 보안 - [ ] 민감 정보가 암호화되어 있는가? - [ ] 데이터베이스 접근이 제한되어 있는가? - [ ] 백업 데이터가 안전하게 보관되어 있는가? - [ ] 로그 파일이 안전하게 관리되어 있는가? ## 🚨 보안 사고 대응 ### 사고 대응 절차 1. **즉시 조치**: 의심스러운 접근 차단 2. **영향 평가**: 피해 범위 확인 3. **복구 작업**: 시스템 정상화 4. **사후 분석**: 원인 분석 및 재발 방지 ### 비상 연락처 - **시스템 관리자**: [연락처] - **보안 담당자**: [연락처] - **시놀로지 지원**: [연락처] 이 보안 가이드를 통해 안전하고 편리한 Todo 시스템을 구축할 수 있습니다.