--- status: stable page_id: A06_Login related_pages: ["[[A06_Login/A06_frontend]]", "[[A07_Register/A07_backend]]", "[[A09_Security/A09_backend]]", "[[auth_rbac]]"] last_updated: 2026-07-12 --- # A06_Login — Backend 세션 쿠키 기반 로그인 + OTP 재인증 + 세션/비밀번호 관리 API. ## 파일 구조 | 파일 | 위치 | 역할 | |---|---|---| | `A06_Login_Router.py` | `A06_Login/A06_Login_Router.py:1-179` | 인증 API 라우터 (prefix `/api/auth`) | | `A06_Login_Schema.py` | `A06_Login/A06_Login_Schema.py:1-20` | 요청 검증 Pydantic 모델 3종 | ## API 엔드포인트 | 엔드포인트 | 위치 | 역할 | |---|---|---| | `POST /api/auth/login/request` | `A06_Login/A06_Login_Router.py:51` | 1차 로그인(이메일+비번). 신규 브라우저/주기 재인증 시 `otp_required` 반환 | | `POST /api/auth/login/verify` | `A06_Login/A06_Login_Router.py:113` | OTP 검증 후 세션 발급 | | `GET /api/auth/session` | `A06_Login/A06_Login_Router.py:145` | 현재 세션 사용자 조회 (id/email/name/role/company_id/is_master) | | `POST /api/auth/logout` | `A06_Login/A06_Login_Router.py:160` | 세션 삭제 + 쿠키 제거 | | `POST /api/auth/password` | `A06_Login/A06_Login_Router.py:170` | 비밀번호 변경 (현재 비번 검증, 재인증 필요 반환) | ## 내부 헬퍼 | 함수 | 위치 | 역할 | |---|---|---| | `_user_agent(request)` | `A06_Login/A06_Login_Router.py:40` | User-Agent 헤더 추출(최대 1000자) | | `_send_otp(user, purpose, label)` | `A06_Login/A06_Login_Router.py:44` | OTP 생성·해시 저장·메일 발송 | | `_finish_login(user, agent)` | `A06_Login/A06_Login_Router.py:87` | 세션 생성·쿠키 설정·last_login 갱신·로그인 기록 | ## 요청 스키마 (Pydantic) | 모델 | 위치 | 필드 | |---|---|---| | `LoginRequest` | `A06_Login/A06_Login_Schema.py:6` | email(EmailStr), password(8~128자) | | `OtpVerifyRequest` | `A06_Login/A06_Login_Schema.py:11` | email(EmailStr), otp_code(정규식 `^\d{6}$`) | | `PasswordChangeRequest` | `A06_Login/A06_Login_Schema.py:16` | current_password, new_password(8~128자), logout_all(기본 True) | ## 로그인 로직 흐름 1. `login/request`: 이메일 조회 → 계정 잠금 확인 → 비밀번호 검증 → 계정 상태 확인(`ACTIVE`/`NO_COMPANY`/`PENDING`만 허용) 2. **OTP 트리거 조건** (둘 중 하나면 OTP 요구): - 주기적 재인증: `last_email_verified_at`이 `EMAIL_REVERIFY_DAYS` 이전 - 신규 브라우저: `has_known_browser()` 미확인 3. `login/verify`: OTP 검증 → 소비 → `last_email_verified_at` 갱신 → `_finish_login` 4. `_finish_login`: 로그인 실패 초기화 → 브라우저 신뢰 등록 → `auth_expires_at = NOW() + 3개월` → 세션 생성 ## 보안 정책 (실 코드 기준) - **비밀번호 실패**: `record_failed_login()` 누적, 5회 이상 & ADMIN_EMAIL 설정 시 보안 경고 메일 발송 - **계정 잠금**: `account_locked_until`이 미래면 423 반환 - **로그인 기록**: 모든 시도를 `record_login()`으로 기록(성공/실패 사유 포함) ## 의존성 (공통 유틸) - [[common_util]] — `common_util_auth`(세션/OTP/해시), `common_util_auth_repository`(DB 조회), `common_util_email`(메일 발송) - 인증/세션/OTP 정책 상세 → [[auth_rbac]] - `config_system` — `ADMIN_EMAIL`, `EMAIL_REVERIFY_DAYS` ## ⚠️ 계획서와 실 코드 불일치 [[auth_rbac]]는 계획서 기준(OTP 5분·재시도 3회, 세션 4h/12h, 5회 실패 15분 잠금)으로 기술돼 있으나, 실 코드는 `auth_expires_at = NOW() + 3개월` 갱신·브라우저 신뢰 기반 OTP·5회 실패 시 관리자 알림 메일 방식. 정책 상세 검증 필요 — 규칙 6(실 코드 우선)에 따라 auth_rbac 갱신 대상.