# 로그인 & 보안 시스템 구현 계획서 (v1.0) ## 1. 프로젝트 개요 ### 1.1 목표 - B2B SaaS 환경에서 **마스터-사용자** 기반 조직 관리 - **이메일 인증** 기반 강화된 보안 - **시스템관리자** 전체 감시/관리 권한 - 개인정보보호법 준수 ### 1.2 적용 대상 - 30인 미만 소규모 기업 (공용 이메일 허용) - 개인별 계정 운영 (마스터 승인제) - 한국 개인정보보호법 준수 필수 --- ## 2. 계정 유형 및 역할 (3가지) ### 2.1 마스터 (Master / Company Admin) **정의**: 회사 최초 가입자 (회사당 1명 권장, 복수 가능) **권한**: - 회사 정보 입력/수정 - 초대코드 발급 및 관리 - 신규 팀원 가입 승인/거부 - 팀원 목록 조회 - 팀원 강제 제거 (내보내기) - 회사 구독/결제 관리 - 회사 대시보드 (기본 통계) **DB 구분**: `users.is_master = true` --- ### 2.2 팀원 (Member / Regular User) **정의**: 마스터가 승인한 회사 소속 사용자 **권한**: - 서비스 기능 일반 이용 (B04~B09 워크플로우) - 개인 프로필 수정 - 프로젝트 생성/수정/조회 **DB 구분**: `users.is_master = false` **상태 관리**: - `PENDING` (승인 대기중) - `ACTIVE` (승인됨, 이용 가능) - `INACTIVE` (마스터가 내보냄, 접근 불가) - `REJECTED` (마스터가 거부함) --- ### 2.3 시스템관리자 (System Administrator) **정의**: 개발사 (Aislo) 측 운영 담당자 = **당신** **권한**: - 모든 회사 데이터 조회 (슈퍼유저) - 회사/사용자 계정 생성/삭제/수정 - 회사별 구독 상태 관리 - 전체 시스템 Q&A/요청사항 처리 (회사 메일로 수신 → 검토/수정/삭제) - 관리자 대시보드 - 활성 사용자 수 - 하드웨어 리소스 모니터링 (CPU, 메모리, 디스크) - 소프트웨어 사용 현황 (업로드/다운로드/API 호출 통계) - 에러/경고 로그 모니터링 - 회사별 파일 저장소 사용량 - 마스터 계정 대체 관리 (마스터 부재 시) **DB 구분**: `users.role = 'SYSTEM_ADMIN'` (별도 테이블로 관리) **접근**: 별도 관리 포털 (프론트엔드에서 숨김) --- ## 3. 회원가입 및 조직 연결 흐름 ### 3.1 마스터 가입 흐름 ``` 1단계: 회원가입 시작 └─ 이메일 입력 (예: founder@gmail.com) └─ 비밀번호 입력 2단계: 이메일 인증 └─ 시스템이 해당 이메일로 6자리 OTP 코드 발송 └─ 사용자가 OTP 입력하여 인증 3단계: 회사 정보 입력 └─ 회사명 └─ 사업자번호 (선택) └─ 회사 전화번호 └─ 주소 └─ 대표자명 4단계: 가입 완료 └─ DB에 Company 레코드 생성 └─ 고유 초대코드 자동 생성 (예: ABC-789-XYZ) └─ users.is_master = true로 설정 └─ 마스터 대시보드로 이동 ``` --- ### 3.2 팀원 가입 흐름 (요청 기반) ``` 1단계: 팀원이 가입 신청 └─ 이메일 입력 (예: member@gmail.com) └─ 비밀번호 입력 2단계: 이메일 인증 └─ OTP 입력하여 인증 3단계: 회사 정보 입력 (선택지) ├─ [새 회사 등록하기] → 마스터 역할 시작 └─ [기존 회사에 참여하기] → 4단계로 진행 4단계: 회사 선택/검색 └─ "우리 회사가 이미 가입되어 있나요?" └─ 회사명으로 검색 └─ 검색 결과에서 해당 회사 선택 5단계: 승인 요청 └─ [가입 신청 보내기] 버튼 클릭 └─ 백엔드: JoinRequest 레코드 생성 └─ 백엔드: 마스터의 등록된 이메일로 승인 메일 발송 6단계: 마스터의 승인 메일 확인 └─ 마스터가 받은 승인 메일: "홍길동(member@gmail.com)님이 우리 회사에 가입을 신청했습니다. [승인] [거부] 버튼" └─ 마스터가 [승인] 클릭 └─ 팀원 users.status = 'ACTIVE'로 변경 └─ 팀원에게 "승인되었습니다" 메일 발송 7단계: 팀원 로그인 가능 └─ 팀원이 이제 서비스에 로그인 가능 ``` **💡 핵심 차이점** - ❌ 초대코드 쪽지 방식 아님 - ✅ 사용자가 스스로 요청 → 마스터가 메일로 승인 --- ## 4. 이메일 인증 (MFA) 정책 ### 4.1 로그인 프로세스 ``` 1단계: 아이디(이메일)/비밀번호 입력 2단계: 백엔드 검증 (계정 존재, 비밀번호 일치, 상태 = ACTIVE) 3단계: OTP 발송 및 입력 └─ 등록된 이메일로 6자리 OTP 코드 발송 └─ 사용자 입력 (유효시간: 5분) 4단계: 로그인 완료 └─ JWT/세션 토큰 발급 ``` ### 4.2 이메일 인증 주기 | 상황 | 인증 여부 | |------|---------| | **최초 가입 시** | ✅ 필수 | | **로그인할 때마다** | ❌ 아니오 (UX 개선) | | **3개월 주기 재인증** | ✅ 필수 | | **새로운 브라우저/기기** | ✅ 필수 | | **비밀번호 변경 후** | ✅ 필수 | | **관리자가 강제 로그아웃** | ✅ 필수 (재로그인 시) | **구현 방식**: - DB: `users.last_email_verified_at` 컬럼 추가 - 로그인 시점에 "마지막 인증이 3개월 이상 지났는가?" 체크 - 맞으면 이메일 인증 강제 --- ## 5. 세션 관리 정책 ### 5.1 세션 유지 시간 | 시나리오 | 유지 시간 | 이유 | |---------|---------|------| | **기본 세션 유지** | 12시간 | 대용량 파일 업로드 고려 (최대 20GB) | | **활동 중인 경우** | 연장 | 파일 전송/처리 중일 때는 타임아웃 제외 (마지막 요청으로부터 4시간) | | **로그인 유지 옵션** | 최대 30일 | (선택사항) 차후 구현 검토 | ### 5.2 활동 감지 로직 ``` 활동으로 간주하는 API 엔드포인트: - 파일 업로드/다운로드 - 프로젝트 저장 - 데이터 처리 요청 - 대시보드/페이지 조회 세션 연장 방식: - 각 API 호출 시 last_activity_at 컬럼 업데이트 - 만료 판정: if (current_time - last_activity_at > 4시간) → 세션 자동 만료 else if (current_time - created_at > 12시간) → 세션 자동 만료 ``` ### 5.3 세션 만료 처리 - ✅ 프론트엔드: 만료 5분 전 알림 ("5분 후 로그아웃됩니다") - ✅ 백엔드: 만료 후 모든 요청 거부 (401 Unauthorized) - ✅ 재로그인 필요 --- ## 6. 비밀번호 정책 (Google 기준 참고) ### 6.1 비밀번호 입력 요구사항 | 요구사항 | 기준 | |---------|------| | **최소 길이** | 8자 이상 | | **대문자** | A-Z 최소 1개 (필수 아님, 권장) | | **소문자** | a-z 최소 1개 (필수 아님, 권장) | | **숫자** | 0-9 최소 1개 (필수 아님, 권장) | | **특수문자** | !@#$%^&* 등 (필수 아님, 권장) | **정책**: Google과 유사하게 "권장이지만 강제하지 않음" - 이유: 강제 정책은 사용자 이탈 증가 ### 6.2 비밀번호 저장 (DB) - ❌ 평문 저장 절대 금지 - ✅ bcrypt 또는 PBKDF2 해시 저장 - ✅ Salt 포함 (bcrypt 내장) - 해시 라운드: 12라운드 이상 ### 6.3 비밀번호 변경 - 이전 비밀번호와 동일하면 거부 - 변경 시 "모든 기기 로그아웃" 옵션 제공 (선택사항) - 마스터의 경우: 보안상 권장 --- ## 7. 로그인 이력 및 감시 (Audit Log) ### 7.1 기록할 정보 **로그인 성공**: - `user_id` - `email` - `login_timestamp` - `user_agent` (브라우저명, OS 정보만 - 기기 ID 아님) - `last_activity_at` (업데이트 시작 시점) **로그인 실패**: - `email` (계정 존재 여부 관계없이) - `failed_timestamp` - `failure_reason` (계정없음 / 비밀번호틀림 / 이메일인증실패 / 계정비활성 등) - `user_agent` **프로그램 사용 이력**: - `user_id` - `action_type` (파일업로드 / 프로젝트저장 / 데이터처리 등) - `action_timestamp` - `resource_id` (프로젝트ID, 파일ID 등) - `status` (성공/실패) ### 7.2 IP 주소 수집 (법적 고려) **❌ 수집하지 않음 (권장)** - 이유: 국내 개인정보보호법상 개인정보이며, 별도 동의 + 암호화 필수 - 법적 리스크 회피 **만약 수집한다면**: - ✅ 회원가입 약관에 명시 ("보안 목적으로 로그인 IP 기록") - ✅ 개인정보 처리방침에 명시 (보관기간: 3개월) - ✅ DB에 암호화하여 저장 (평문 금지) - ✅ 마스터/시스템관리자만 조회 가능 **현재 권장**: IP 수집 제외, User-Agent(브라우저/OS)만 저장 ### 7.3 데이터 보관 기간 | 로그 타입 | 보관 기간 | 자동 삭제 | |----------|---------|---------| | 로그인 성공 | 1년 | ✅ 자동 삭제 | | 로그인 실패 | 6개월 | ✅ 자동 삭제 | | 프로그램 사용 | 2년 | ✅ 자동 삭제 | | 삭제된 사용자 로그 | 법적 보유 기간 후 삭제 | ✅ 자동 삭제 | --- ## 8. 시스템관리자 대시보드 ### 8.1 기능 #### 📊 시스템 모니터링 - 활성 사용자 수 (실시간) - 활성 회사 수 - 일일/주간/월간 활동 통계 그래프 #### 💾 리소스 모니터링 - **하드웨어** - CPU 사용률 (%) - 메모리 사용률 (%) - 디스크 사용량 (GB) - 활성 프로세스 수 - **소프트웨어** - 일일 API 호출 수 - 업로드된 총 파일 크기 (GB) - 다운로드 횟수 - 동시 접속자 수 #### 👥 회사 관리 - 회사 목록 조회 (가입일, 상태, 사용자 수) - 회사 구독 상태 확인 - 마스터 정보 확인 및 변경 - 회사 삭제 (강제) #### 👤 사용자 관리 - 전체 사용자 목록 - 사용자 상태 변경 (ACTIVE / INACTIVE / REJECTED) - 비밀번호 초기화 (관리자용) - 사용자 로그인 이력 조회 #### 📧 Q&A / 요청사항 관리 - 회사 메일로 수신된 Q&A/요청 목록 - 상태 관리 (NEW / REVIEWED / RESOLVED / DELETED) - 요청 내용 검토 및 수정 - 요청 삭제 및 히스토리 관리 #### ⚠️ 에러/경고 로그 - 시스템 에러 로그 조회 - 비정상 로그인 시도 (같은 계정 5회 이상 실패) - 대역폭 초과 경고 - 데이터베이스 에러 --- ## 9. 보안 방어 로직 ### 9.1 무차별 대입(Brute Force) 공격 방지 ``` 로그인 실패 5회 이상 → 계정 15분 잠금 - 로그인 실패 횟수 저장: login_failures (컬럼) - 마지막 실패 시간: last_failed_at (컬럼) - 15분 후 자동 해제 시스템관리자에게 알림 (캡파 or SMS) - 5회 실패 시 시스템 관리자 메일 알림 - 10회 이상 실패 시 경고 레벨 상향 ``` ### 9.2 동시 로그인 처리 (개인별 계정 기준) - ✅ 동시 로그인 허용 - 이유: 개인별 계정이므로 가정용 + 업무 기기 동시 사용 고려 - 다만, 의심 로그인 시 알림 (예: 다른 국가/지역에서 동시 접속) ### 9.3 세션 토큰 보안 (확정: Secure HttpOnly 세션 쿠키) **선택**: Secure HttpOnly 세션 쿠키 (JWT 아님) - **이유**: 모놀리식 구조 + 중소규모 + 보안 최우선 + 세션 추적 필요 **쿠키 설정**: - ✅ **쿠키 이름**: `session_id` - ✅ **유효 시간**: 12시간 - ✅ **Secure 플래그**: true (HTTPS만 전송) - ✅ **HttpOnly 플래그**: true (JS 접근 불가, XSS 방지) - ✅ **SameSite**: Lax (CSRF 방지) **세션 저장소**: MariaDB `sessions` 테이블 ```sql CREATE TABLE sessions ( id VARCHAR(255) PRIMARY KEY, user_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, last_activity_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); ``` --- ## 10. 이메일 발송 인프라 ### 10.1 발송해야 할 메일 유형 | 메일 타입 | 수신자 | 발송 타이밍 | 중요도 | |----------|--------|-----------|------| | **가입 확인 메일** | 신규 가입자 | 가입 완료 직후 | 🔴 필수 | | **OTP 인증 코드** | 로그인/재인증 자 | 로그인 시도 직후 | 🔴 필수 | | **팀원 승인 요청** | 마스터 | 팀원이 가입 신청 시 | 🔴 필수 | | **가입 승인/거부 결과** | 팀원 | 마스터 승인/거부 직후 | 🟠 중요 | | **3개월 재인증 알림** | 모든 사용자 | 3개월마다 정기적 | 🟠 중요 | | **세션 만료 알림** | 활동 중인 사용자 | 만료 5분 전 | 🟡 알림 | | **비밀번호 변경 알림** | 해당 사용자 | 변경 직후 | 🟡 알림 | | **Q&A 답변 회신** | 문의자 | 시스템 관리자 처리 후 | 🟡 알림 | ### 10.2 메일 발송 서비스 **권장 선택**: AWS SES, SendGrid, 또는 한국 국내 서비스 (Solapi, Stibee) **이유**: - 높은 배송 성공률 (99% 이상) - 빠른 발송 속도 (즉시~1초) - 스팸 필터 회피 (신뢰도 높음) - API 기반 통합 용이 **구현 방식**: - FastAPI 백엔드에서 비동기 작업 큐 (Celery + Redis) - 실패한 메일은 재시도 로직 (최대 3회) --- ## 11. DB 스키마 (최소 필수 테이블) ### 11.1 companies 테이블 ```sql CREATE TABLE companies ( id INT AUTO_INCREMENT PRIMARY KEY, company_name VARCHAR(255) NOT NULL, company_code VARCHAR(20) UNIQUE NOT NULL, -- 초대코드 역할 business_number VARCHAR(20), phone_number VARCHAR(20), address VARCHAR(500), representative_name VARCHAR(100), master_user_id INT NOT NULL, status ENUM('ACTIVE', 'INACTIVE', 'SUSPENDED') DEFAULT 'ACTIVE', subscription_status ENUM('FREE', 'TRIAL', 'PAID', 'EXPIRED') DEFAULT 'FREE', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (master_user_id) REFERENCES users(id) ); ``` ### 11.2 users 테이블 ```sql CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, company_id INT, is_master BOOLEAN DEFAULT FALSE, status ENUM('PENDING', 'ACTIVE', 'INACTIVE', 'REJECTED') DEFAULT 'PENDING', last_email_verified_at TIMESTAMP, login_failures INT DEFAULT 0, -- 실패 횟수 last_failed_at TIMESTAMP, -- 마지막 실패 시간 account_locked_until TIMESTAMP, -- 계정 잠금 해제 시간 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (company_id) REFERENCES companies(id) ); ``` ### 11.3 join_requests 테이블 ```sql CREATE TABLE join_requests ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, company_id INT NOT NULL, status ENUM('PENDING', 'APPROVED', 'REJECTED') DEFAULT 'PENDING', requested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, reviewed_at TIMESTAMP, reviewed_by INT, -- 마스터 user_id review_comment TEXT, FOREIGN KEY (user_id) REFERENCES users(id), FOREIGN KEY (company_id) REFERENCES companies(id), FOREIGN KEY (reviewed_by) REFERENCES users(id) ); ``` ### 11.4 login_logs 테이블 ```sql CREATE TABLE login_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, email VARCHAR(255), -- 실패 시 user_id가 NULL일 수 있음 status ENUM('SUCCESS', 'FAILURE') NOT NULL, failure_reason VARCHAR(100), user_agent TEXT, -- 브라우저, OS 정보만 login_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id), INDEX (user_id), INDEX (login_at) ); ``` ### 11.5 activity_logs 테이블 ```sql CREATE TABLE activity_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, action_type ENUM('FILE_UPLOAD', 'FILE_DOWNLOAD', 'PROJECT_SAVE', 'DATA_PROCESS', 'PAGE_VIEW'), action_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, resource_id VARCHAR(255), -- 프로젝트/파일 ID status ENUM('SUCCESS', 'FAILURE') DEFAULT 'SUCCESS', details JSON, -- 추가 정보 FOREIGN KEY (user_id) REFERENCES users(id), INDEX (user_id), INDEX (action_at) ); ``` ### 11.6 system_admin_logs 테이블 ```sql CREATE TABLE system_admin_logs ( id INT AUTO_INCREMENT PRIMARY KEY, admin_user_id INT NOT NULL, action_type ENUM('USER_DELETE', 'COMPANY_DELETE', 'QA_MODIFY', 'QA_DELETE', 'RESET_PASSWORD'), target_id INT, -- 대상 사용자/회사 ID action_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, details JSON, FOREIGN KEY (admin_user_id) REFERENCES users(id), INDEX (admin_user_id), INDEX (action_at) ); ``` ### 11.7 support_requests 테이블 (Q&A) ```sql CREATE TABLE support_requests ( id INT AUTO_INCREMENT PRIMARY KEY, company_id INT, user_email VARCHAR(255), subject VARCHAR(255) NOT NULL, message TEXT NOT NULL, status ENUM('NEW', 'REVIEWED', 'RESOLVED', 'DELETED') DEFAULT 'NEW', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, reviewed_at TIMESTAMP, reviewed_by INT, -- 시스템 관리자 user_id review_notes TEXT, FOREIGN KEY (company_id) REFERENCES companies(id), FOREIGN KEY (reviewed_by) REFERENCES users(id), INDEX (status), INDEX (created_at) ); ``` --- ## 12. 구현 우선순위 (Phase) ### Phase 1 (필수): 기본 인증 및 회원가입 - [ ] 회원가입 로직 (이메일 OTP 포함) - [ ] 마스터 회사 정보 입력 - [ ] 로그인 로직 (비밀번호 + 이메일 OTP) - [ ] 세션/토큰 관리 - [ ] 사용자 상태 관리 (ACTIVE/INACTIVE) ### Phase 2 (필수): 팀원 가입 승인제 - [ ] 팀원 가입 신청 기능 - [ ] 가입 요청 알림 메일 (마스터 수신) - [ ] 마스터 승인/거부 대시보드 - [ ] 승인/거부 결과 메일 (팀원 수신) ### Phase 3 (중요): 보안 강화 - [ ] 3개월 주기 재인증 로직 - [ ] 로그인 이력 기록 (login_logs) - [ ] 무차별 대입 공격 방지 (5회 실패 잠금) - [ ] 활동 로그 기록 (activity_logs) ### Phase 4 (중요): 마스터 관리 기능 - [ ] 팀원 목록 조회 - [ ] 팀원 강제 내보내기 (status = INACTIVE) - [ ] 마스터 대시보드 (회사 통계) ### Phase 5 (고급): 시스템관리자 포털 - [ ] 관리자 대시보드 - [ ] 회사/사용자 관리 - [ ] Q&A 관리 - [ ] 리소스 모니터링 - [ ] 에러 로그 모니터링 --- ## 13. 주요 검토 사항 ### 13.1 이메일 발송 인프라 - [ ] SMTP 서버 선정 (AWS SES / SendGrid / Solapi) - [ ] 테스트: 가입 메일 발송 성공률 확인 - [ ] 스팸 필터 회피 전략 (SPF, DKIM, DMARC 설정) ### 13.2 비밀번호 해싱 라이브러리 - [ ] Python bcrypt 또는 argon2 설정 - [ ] 라운드 수 결정 (권장: 12 이상) ### 13.3 JWT vs 세션 쿠키 - [ ] 선택: Secure HttpOnly 쿠키 (권장, CSRF/XSS 방어) - [ ] 토큰 만료 시간 설정 ### 13.4 개인정보 처리방침 작성 - [ ] 로그인 이력 수집 목적 명시 - [ ] 보관 기간 명시 - [ ] 사용자 동의 형식 결정 --- ## 14. 다음 단계 1. ✅ **이 계획서 검토** (당신의 피드백) 2. 📝 **세부 기술 사양 작성** (.agent/backend.md 업데이트) 3. 💻 **Phase 1 코드 구현 시작** (회원가입 + 로그인) 4. 🧪 **단위 테스트 작성** 5. 🎨 **프론트엔드 UI/UX 설계** (.agent/frontend.md) 6. 📊 **통합 테스트 및 검증** --- **작성일**: 2026-07-07 **담당자**: 시스템 설계 팀 **상태**: 초안 (v1.0)