# 생체인증 가이드 (Credential Management API) ## 📱 개요 TWA(Trusted Web Activity) 환경에서 WebAuthn / Credential Management API를 활용한 생체인증 기능을 제공합니다. ## ✨ 주요 기능 ### 1. 생체인증 등록 - 로그인 후 생체인증(지문, 얼굴 인식 등)을 등록할 수 있습니다 - 사용자의 사번과 이름으로 credential을 생성합니다 - 등록된 credential은 Supabase에 안전하게 저장됩니다 ### 2. 생체인증 로그인 - 등록된 생체인증으로 빠르게 로그인할 수 있습니다 - 비밀번호 입력 없이 지문/얼굴 인식만으로 인증됩니다 - 플랫폼 인증 기능을 사용하여 보안성이 높습니다 ### 3. 생체인증 해제 - 필요시 생체인증을 해제할 수 있습니다 - 해제 후 다시 등록할 수 있습니다 ## 🏗️ 아키텍처 ### Frontend (Vue.js/TypeScript) #### 1. Composable: `useBiometric.ts` ```typescript // 생체인증 지원 여부 확인 await biometric.checkSupport() // 생체인증 등록 const result = await biometric.register(employeeId, userName) // 생체인증 로그인 const result = await biometric.authenticate() // 생체인증 해제 const result = await biometric.unregister() ``` #### 2. 상태 관리 - `isSupported`: 브라우저가 WebAuthn을 지원하는지 여부 - `isAvailable`: 플랫폼 생체인증이 사용 가능한지 여부 - `isEnrolled`: 사용자가 생체인증을 등록했는지 여부 - `canUse`: 생체인증을 사용할 수 있는지 여부 - `canRegister`: 생체인증을 등록할 수 있는지 여부 - `canAuthenticate`: 생체인증으로 로그인할 수 있는지 여부 ### Backend (Flask/Python) #### 1. 생체인증 등록 Challenge ``` POST /api/biometric/register-challenge { "employeeId": "20240001" } ``` **Response:** ```json { "success": true, "challenge": "random_base64_string", "userId": "20240001" } ``` #### 2. Credential 등록 ``` POST /api/biometric/register { "employeeId": "20240001", "credential": { "id": "credential_id", "rawId": "base64_raw_id", "response": { "clientDataJSON": "base64_client_data", "attestationObject": "base64_attestation" }, "type": "public-key" } } ``` #### 3. 로그인 Challenge ``` POST /api/biometric/login-challenge { "employeeId": "20240001" } ``` #### 4. 생체인증 로그인 ``` POST /api/biometric/login { "employeeId": "20240001", "assertion": { "id": "credential_id", "rawId": "base64_raw_id", "response": { "clientDataJSON": "base64_client_data", "authenticatorData": "base64_auth_data", "signature": "base64_signature" }, "type": "public-key" } } ``` **Response:** ```json { "success": true, "user": { "id": 1, "employee_id": "20240001", "name": "홍길동", "email": "hong@humetro.busan.kr", "department_id": 1 } } ``` #### 5. 생체인증 해제 ``` POST /api/biometric/unregister { "employeeId": "20240001" } ``` ## 🗄️ 데이터베이스 스키마 ### biometric_credentials 테이블 ```sql CREATE TABLE IF NOT EXISTS public.biometric_credentials ( id SERIAL PRIMARY KEY, employee_id VARCHAR(50) NOT NULL REFERENCES public.users(employee_id) ON DELETE CASCADE, credential_id VARCHAR(255) UNIQUE NOT NULL, credential_data JSONB NOT NULL, created_at TIMESTAMPTZ DEFAULT NOW(), last_used_at TIMESTAMPTZ, UNIQUE(employee_id, credential_id) ); CREATE INDEX idx_biometric_employee_id ON public.biometric_credentials(employee_id); CREATE INDEX idx_biometric_credential_id ON public.biometric_credentials(credential_id); ``` **필드 설명:** - `employee_id`: 사용자 사번 (외래키) - `credential_id`: WebAuthn credential ID (고유값) - `credential_data`: Credential 전체 데이터 (JSON) - `created_at`: 등록 일시 - `last_used_at`: 마지막 사용 일시 ## 🔐 보안 고려사항 ### 1. Challenge-Response 인증 - 서버에서 생성한 랜덤 challenge를 사용합니다 - Challenge는 세션에 저장되고 10분간 유효합니다 - 각 인증 시도마다 새로운 challenge를 생성합니다 ### 2. 플랫폼 인증 - `authenticatorAttachment: 'platform'`을 사용하여 기기 내장 생체인식만 허용합니다 - `userVerification: 'required'`로 사용자 검증을 필수로 합니다 ### 3. Credential 검증 - 서버에 저장된 credential과 비교하여 검증합니다 - 실제 프로덕션에서는 공개키 암호화를 사용한 서명 검증이 필요합니다 (현재는 간소화된 버전) ### 4. 세션 관리 - 생체인증 성공 시 Flask 세션에 사용자 정보를 저장합니다 - 세션은 httponly 쿠키로 안전하게 관리됩니다 ## 📱 TWA 환경에서의 동작 ### 1. Android TWA - Chrome의 WebAuthn API를 통해 Android 생체인식(지문, 얼굴)을 사용합니다 - Android Keystore에 안전하게 credential이 저장됩니다 - Google Play Services를 통한 FIDO2 인증을 지원합니다 ### 2. 지원 여부 확인 ```javascript // PublicKeyCredential API 지원 확인 if (!window.PublicKeyCredential) { console.log('생체인증 미지원') } // 플랫폼 인증 가능 여부 const available = await PublicKeyCredential .isUserVerifyingPlatformAuthenticatorAvailable() ``` ### 3. 사용자 경험 1. 로그인 페이지에서 생체인증 가능 여부를 자동으로 확인합니다 2. 등록된 생체인증이 있으면 "생체인증으로 로그인" 버튼이 표시됩니다 3. 버튼 클릭 시 기기의 생체인증 UI가 표시됩니다 4. 인증 성공 시 자동으로 로그인됩니다 ## 🚀 사용 방법 ### 1. 생체인증 등록 (로그인 후) ```typescript import { useBiometric } from '@/composables/useBiometric' const biometric = useBiometric() // 지원 여부 확인 await biometric.checkSupport() if (biometric.canRegister.value) { // 등록 const result = await biometric.register(employeeId, userName) if (result.success) { console.log('생체인증 등록 성공') } } ``` ### 2. 생체인증 로그인 ```typescript if (biometric.canAuthenticate.value) { const result = await biometric.authenticate() if (result.success && result.user) { // 로그인 성공 authStore.setUser(result.user) router.push('/') } } ``` ### 3. LoginView에서의 통합 ```vue ``` ## 🔧 문제 해결 ### 1. 생체인증이 표시되지 않음 - 브라우저가 WebAuthn을 지원하는지 확인 - HTTPS 환경에서만 동작 (localhost는 예외) - 기기에 생체인증이 설정되어 있는지 확인 ### 2. 등록 실패 - Challenge가 만료되었을 수 있음 (10분) - 네트워크 연결 확인 - 콘솔 로그에서 에러 메시지 확인 ### 3. 로그인 실패 - 등록된 credential이 삭제되었을 수 있음 - localStorage에서 `biometric_credential_id` 확인 - 데이터베이스에 credential이 존재하는지 확인 ## 📝 TODO (향후 개선사항) 1. **서명 검증 구현** - 현재는 credential_id만 비교하는 간소화된 버전 - 실제 공개키 암호화 서명 검증 추가 필요 2. **여러 Credential 지원** - 한 사용자가 여러 기기에서 생체인증 등록 가능하도록 3. **Credential 관리 UI** - 등록된 기기 목록 표시 - 개별 credential 삭제 기능 4. **마지막 사용 시간 업데이트** - 로그인 시 `last_used_at` 필드 업데이트 5. **에러 로깅 개선** - 생체인증 관련 에러를 audit_logs에 기록 ## 🔗 참고 자료 - [WebAuthn Guide](https://webauthn.guide/) - [MDN - Web Authentication API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API) - [FIDO Alliance](https://fidoalliance.org/) - [Chrome WebAuthn](https://developers.google.com/web/updates/2018/05/webauthn)