Architecture
SnowPass 컴포넌트 구조·멀티테넌트·토큰 모델·보안 통제
Component Architecture
Multi-tenancy (멀티테넌트 모델)
tenant (테넌트) = RP 애플리케이션 1개. 데모 기준 두 테넌트가 등록된다.
| 항목 | tmoney-home | card-system |
|---|---|---|
| rp_id (WebAuthn RP ID) | localhost (운영: tmoney.co.kr) | localhost |
| allowed_origins | http://localhost:8081 | http://localhost:5174, :8082 |
| public_api_key | pk_... — 브라우저 노출 가능 | 〃 |
| api_secret | sk_... — 서버 보관, SHA-256 해시만 저장 | 〃 |
- 사용자·credential·ceremony·audit 모든 데이터가 tenant 로 격리된다.
- 사용자 식별: RP 는 자체 생성한 System User ID (UUID) 를
externalUserId로 전달한다. 사용자 ID (login id, 최대 16자 — 사번·사이트 아이디) 와 email 은 표시·검색용 부가 속성으로 함께 전달한다 (PII 최소화: WebAuthn userHandle 에는 UUID 만 사용). - RP ID 는 도메인에 바인딩된다. rpId 가 다르면 기존 passkey 는 동작하지 않으므로, 운영 도메인 확정 후 테넌트 설정을 맞춰야 한다 (on-prem passkey 운영의 1순위 주의사항).
- CORS 는 테넌트의 allowed_origins 기준으로 요청 단위 검증한다.
Token Model (토큰 수명 체계)
| 토큰 | 발급 | 수명 | 사용 | 저장 |
|---|---|---|---|---|
register token sprt_* | private /v1/register/token | 300초 | 등록 ceremony 1회 | SHA-256 해시만 DB |
auth token spat_* | private /v1/auth/token | 180초 | 인증 ceremony 1회 | 〃 |
| result token (JWT RS256) | ceremony 성공 시 | 120초 | RP 백엔드 검증 1회 (jti 1회용) | 서버 무상태, jti 캐시 |
핵심 흐름: RP 백엔드 → (private) ceremony token 발급 → 브라우저 → (public) ceremony 수행 →
result token → RP 백엔드 → (private) /v1/auth/verify 검증. 브라우저는 api secret 을 절대
보지 못하고, RP 백엔드는 ceremony 내용을 알 필요가 없다.
result token claims: iss, sub(externalUserId), aud(tenantCode), iat/exp, jti,
factor (PASSKEY/TOTP/RECOVERY/BYPASS), amr (swk/otp/rcvr/bypass), cid (credential id).
검증은 /v1/auth/verify (jti 1회용 보장) 를 권장하고, JWKS 오프라인 검증도 가능하다.
Data Model
기타: audit_log (전 이벤트), admin_users, signing_keys (RS256 키 영속화 — 재시작 후에도
JWKS kid 안정).
Cryptography Inventory (암호화 인벤토리)
| 대상 | 방식 | 비고 |
|---|---|---|
| TOTP secret 저장 | AES-256-GCM, 레코드별 12B IV | 키는 설정 주입 (운영: KMS/HSM 권장) |
| api secret / ceremony token | SHA-256 해시 저장 | 고엔트로피 값이므로 salt 불요 |
| recovery / bypass code, 비밀번호 | BCrypt | 저엔트로피 → 적응형 해시 |
| result token | RS256 (RSA-2048) | kid + JWKS 공개 |
| WebAuthn 서명 검증 | ES256 등 (COSE) | Yubico webauthn-server-core 위임 |
Security Controls (보안 통제)
- Challenge: 서버 생성·서버 보관·1회용·짧은 TTL. 클라이언트가 echo 한 값은 신뢰하지 않음.
- Origin/rpId 검증: 테넌트 allowed_origins / rp_id 와 불일치 시 거부 (phishing-resistance 핵심).
- TOTP replay 차단: RFC 6238 MUST —
last_used_timestep초과만 허용. drift ±1 step (90초 창). - Lockout: 연속 5회 실패 → 5분 잠금 (NIST SP 800-63B throttling). 관리자 unlock 제공.
- Last-credential guard: 마지막 credential 삭제 거부 (
LAST_CREDENTIAL409) — 자기 잠금 방지. - excludeCredentials: 동일 인증기 중복 등록 방지.
- signature counter: 인증 시 갱신·저장 (clone 탐지 신호. synced passkey 는 0 고정 허용).
- Recovery: 1회용 recovery code 10개 (BCrypt, 발급 응답에서만 평문) + 관리자 발급 bypass code (기본 15분 TTL). SMS 복구는 제공하지 않는다 (FIDO 권고).
- Audit log: 등록·인증 성공/실패·replay 차단·lockout·관리 행위 전체 기록. secret·생체정보는 기록하지 않음.
Deployment View (배포 뷰)
- Public API 만 브라우저에 노출되면 되고 (HTTPS 필수 — WebAuthn 은 secure context 요구), private/admin API 는 내부망으로 제한할 수 있다.
- 로컬 데모 포트: SnowPass 8080 / admin-web 5173 / 홈페이지 8081 / Card System 8082 / card-admin-web 5174 / PostgreSQL 55432(docker).
Technology Stack
| 계층 | 선택 | 근거 |
|---|---|---|
| Server | Java 17, Spring Boot 3.3, MyBatis, Flyway | 발주 요건 |
| WebAuthn | com.yubico:webauthn-server-core 2.9.0 | ceremony 전체 API + CredentialRepository SPI, Java 8 bytecode |
| TOTP | com.eatthepath:java-otp 0.4.0 + zxing | RFC 6238 검증 라이브러리 + QR |
| JWT | nimbus-jose-jwt | RS256 + JWKS |
| JS SDK | TypeScript + @simplewebauthn/browser v13 | base64url 마샬링·conditional UI 대비 |
| Admin/데모 프론트 | Vite + React + shadcn/ui | 발주 요건 |