Tr_Code/BIOMETRIC_AUTH_GUIDE.md

318 lines
8.3 KiB
Markdown

# 생체인증 가이드 (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
<template>
<!-- 생체인증 버튼 -->
<button
v-if="biometric.canAuthenticate.value"
@click="handleBiometricLogin"
:disabled="biometric.loading.value"
>
생체인증으로 로그인
</button>
<!-- 등록 안내 -->
<div v-else-if="!biometric.isEnrolled.value && biometric.canUse.value">
로그인 생체인증을 등록할 있습니다.
</div>
</template>
<script setup>
import { useBiometric } from '@/composables/useBiometric'
const biometric = useBiometric()
onMounted(async () => {
await biometric.checkSupport()
})
async function handleBiometricLogin() {
const result = await biometric.authenticate()
// 처리...
}
</script>
```
## 🔧 문제 해결
### 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)