Files
Todo-Project/docs/SECURITY.md
Hyungi Ahn 761757c12e Initial commit: Todo Project with dashboard, classification center, and upload functionality
- 📱 PWA 지원: 홈화면 추가 가능한 Progressive Web App
- 🎨 M-Project 색상 스키마: 하늘색, 주황색, 회색, 흰색 일관된 디자인
- 📊 대시보드: 데스크톱 캘린더 뷰 + 모바일 일일 뷰 반응형 디자인
- 📥 분류 센터: Gmail 스타일 받은편지함으로 스마트 분류 시스템
- 🤖 AI 분류 제안: 키워드 기반 자동 분류 제안 및 일괄 처리
- 📷 업로드 모달: 데스크톱(파일 선택) + 모바일(카메라/갤러리) 최적화
- 🏷️ 3가지 분류: Todo(시작일), 캘린더(마감일), 체크리스트(무기한)
- 📋 체크리스트: 진행률 표시 및 완료 토글 기능
- 🔄 시놀로지 연동 준비: 메일플러스 연동을 위한 구조 설계
- 📱 반응형 UI: 모든 페이지 모바일 최적화 완료
2025-09-19 08:52:49 +09:00

12 KiB

보안 가이드 (SECURITY.md)

🔐 보안 철학

Todo-Project는 개인용 도구로 설계되어 편의성적절한 보안 사이의 균형을 추구합니다.

보안 원칙

  • Trust but Verify: 신뢰할 수 있는 기기에서는 간편하게, 의심스러운 접근은 차단
  • 최소 권한: 필요한 최소한의 권한만 부여
  • 개인 최적화: 개인 사용에 최적화된 보안 모델

🛡️ 보안 레벨

1. Minimal (개인용 권장)

SECURITY_MINIMAL = {
    "device_remember_days": 30,      # 30일간 기기 기억
    "require_password": False,       # 기기 등록 후 비밀번호 불필요
    "session_timeout": 0,            # 무제한 세션
    "biometric_optional": True,      # 생체 인증 선택사항
    "auto_login": True               # 자동 로그인 활성화
}

적합한 환경: 개인 기기 (내 폰, 내 컴퓨터)에서만 사용

2. Balanced (일반 권장)

SECURITY_BALANCED = {
    "device_remember_days": 7,       # 7일간 기기 기억
    "require_password": True,        # 주기적 비밀번호 확인
    "session_timeout": 24*60,        # 24시간 세션
    "biometric_optional": True,      # 생체 인증 선택사항
    "auto_login": False              # 수동 로그인
}

적합한 환경: 가끔 다른 기기에서도 접근하는 경우

3. Secure (높은 보안)

SECURITY_SECURE = {
    "device_remember_days": 1,       # 1일간만 기기 기억
    "require_password": True,        # 매번 비밀번호 확인
    "session_timeout": 60,           # 1시간 세션
    "biometric_required": True,      # 생체 인증 필수
    "auto_login": False              # 수동 로그인
}

적합한 환경: 민감한 정보가 포함된 경우

🔑 인증 시스템

기기 등록 방식

기기 식별

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자리 축약

기기 등록 프로세스

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 토큰 구조

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")

토큰 검증

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

🔒 데이터 보안

데이터베이스 보안

비밀번호 해싱

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)

민감 정보 암호화

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 강제

from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

# 프로덕션에서 HTTPS 강제
if not settings.DEBUG:
    app.add_middleware(HTTPSRedirectMiddleware)

CORS 설정

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=["*"],
)

🚨 보안 모니터링

로그인 시도 모니터링

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)
        )

의심스러운 활동 감지

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)

🔧 보안 설정

환경 변수 보안

# .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

보안 헤더

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 시스템을 구축할 수 있습니다.