docs: [esign] 전자계약 고도화 기획서 — 필기 문구 확인(Handwriting Verification)
This commit is contained in:
1
INDEX.md
1
INDEX.md
@@ -196,6 +196,7 @@ DB 도메인별:
|
||||
| [hr/](features/hr/) | 인사관리 |
|
||||
| [crm/README.md](features/crm/README.md) | CRM |
|
||||
| [esign/README.md](features/esign/README.md) | 전자서명 |
|
||||
| [esign/handwriting-verification.md](features/esign/handwriting-verification.md) | 전자서명 고도화 — 필기 문구 확인 (HWR 인식, 유사도 검증, 보험업법 준용) |
|
||||
| [equipment/README.md](features/equipment/README.md) | 설비관리 (API Phase 1 완료 + DB 스키마) |
|
||||
| [boards/README.md](features/boards/README.md) | 게시판 |
|
||||
| [ai/README.md](features/ai/README.md) | AI 기능 (리포트, 토큰사용량, 단가설정, R2 비용추적) |
|
||||
|
||||
779
features/esign/handwriting-verification.md
Normal file
779
features/esign/handwriting-verification.md
Normal file
@@ -0,0 +1,779 @@
|
||||
# 전자계약 eSign 고도화 — 필기 문구 확인 (Handwriting Verification)
|
||||
|
||||
> **작성일**: 2026-03-22
|
||||
> **상태**: 기획 중
|
||||
> **담당**: R&D실
|
||||
> **v1 참조**: [features/esign/README.md](README.md), [projects/e-sign/technical-design.md](../../projects/e-sign/technical-design.md)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요
|
||||
|
||||
### 1.1 배경
|
||||
|
||||
현재 eSign v1은 **서명 캔버스(signature_pad)** 또는 **도장 이미지 업로드**로 본인 확인을 수행한다. 이 방식은 "서명했다"는 증거만 남기며, 서명자가 **계약 내용을 실제로 확인했는지** 증명하기 어렵다.
|
||||
|
||||
보험업계에서는 이미 **자필 문구 확인(Handwriting Verification)** 방식을 표준으로 적용하고 있다:
|
||||
|
||||
- "본인은 위 내용을 충분히 이해하고 동의합니다"를 **직접 손으로 따라 쓰기**
|
||||
- 필기 인식(HWR)으로 **일치율을 검증**
|
||||
- 일정 임계값 이상이어야 다음 단계 진행
|
||||
|
||||
### 1.2 목적
|
||||
|
||||
기존 서명/도장에 **필기 문구 확인 단계**를 추가하여, 계약 체결의 법적 증거력과 분쟁 방지 능력을 강화한다.
|
||||
|
||||
### 1.3 핵심 가치
|
||||
|
||||
| 가치 | 설명 |
|
||||
|------|------|
|
||||
| **확인 증거** | "읽고 이해했다"를 자필로 증명 → 분쟁 시 강력한 증거 |
|
||||
| **위변조 방지** | 단순 클릭/터치가 아닌 자필 행위 → 대리 서명 억제 |
|
||||
| **법적 효력** | 보험업법 시행령 기반 자필확인 → 금감원 권고 사항 준용 |
|
||||
| **차별화** | 모두싸인/도큐사인에 없는 자필확인 기능 → SAM 전자계약 경쟁력 |
|
||||
|
||||
### 1.4 v1 vs 고도화 비교
|
||||
|
||||
| 항목 | v1 (현재) | 고도화 (추가) |
|
||||
|------|----------|-------------|
|
||||
| 본인확인 | OTP 인증 | OTP + 필기 문구 확인 |
|
||||
| 서명 방식 | 캔버스 서명 / 도장 업로드 | 기존 유지 + 자필 문구 확인 단계 추가 |
|
||||
| 확인 행위 | 체크박스 동의 | 지정 문구 자필 작성 + 인식 검증 |
|
||||
| 증거 | 서명 이미지 + IP/UA | + 자필 이미지 + 인식 결과 + 일치율 |
|
||||
| 법적 효력 | 전자서명법 준수 | + 보험업법 자필확인 준용 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 사용자 경험 (UX) 설계
|
||||
|
||||
### 2.1 서명 프로세스 흐름 (고도화 후)
|
||||
|
||||
```
|
||||
[기존 v1 유지] [고도화 추가]
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
링크 접속
|
||||
│
|
||||
▼
|
||||
계약 정보 확인
|
||||
│
|
||||
▼
|
||||
OTP 인증 (이메일/카카오톡)
|
||||
│
|
||||
▼
|
||||
PDF 문서 열람 ─────────────────→ 필기 확인 단계 (NEW)
|
||||
│
|
||||
├─ Step 1: 문구 표시
|
||||
│ "본인은 위 내용을 확인하였습니다"
|
||||
│
|
||||
├─ Step 2: 자필 작성 (캔버스)
|
||||
│ 손가락/펜으로 문구를 따라 씀
|
||||
│
|
||||
├─ Step 3: 인식 & 검증
|
||||
│ HWR 엔진이 필기 인식
|
||||
│ 일치율 80% 이상 → 통과
|
||||
│ 미달 → 재작성 안내
|
||||
│
|
||||
▼
|
||||
서명/도장 (기존 방식) ←──────── 통과 시
|
||||
│
|
||||
▼
|
||||
제출 완료
|
||||
```
|
||||
|
||||
### 2.2 필기 확인 화면 구성
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ [SAM 전자계약] Step 2/3 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ 아래 문구를 직접 손으로 써 주세요 │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ 본인은 위 내용을 확인하였습니다 │ │ ← 가이드 문구 (연한 회색)
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────┐ │
|
||||
│ │ │ │
|
||||
│ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │ ← 자필 캔버스 (터치/펜)
|
||||
│ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │
|
||||
│ │ │ │
|
||||
│ └─────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 인식 결과: "본인은 위 내용을 확인하였습니다"│
|
||||
│ 일치율: 92% ✅ │
|
||||
│ │
|
||||
│ [초기화] [다음 단계 →] │
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.3 실패 시 화면
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ │
|
||||
│ 인식 결과: "본인은 위 을 확인합니다" │
|
||||
│ 일치율: 54% ❌ │
|
||||
│ │
|
||||
│ ⚠️ 문구가 정확히 인식되지 않았습니다. │
|
||||
│ 좀 더 또박또박 써 주세요. │
|
||||
│ │
|
||||
│ 💡 팁: 글자 사이 간격을 넓게 쓰면 │
|
||||
│ 인식률이 높아집니다. │
|
||||
│ │
|
||||
│ [초기화] [다시 쓰기] │
|
||||
│ │
|
||||
│ 남은 시도 횟수: 4/5 │
|
||||
│ │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 2.4 디바이스별 최적화
|
||||
|
||||
| 디바이스 | 캔버스 크기 | 입력 방식 | 특이사항 |
|
||||
|----------|-----------|----------|---------|
|
||||
| 모바일 (< 640px) | 전체 너비 × 120px | 손가락 터치 | 가로 모드 권장 안내 |
|
||||
| 태블릿 (640~1024px) | 전체 너비 × 150px | 스타일러스/터치 | Apple Pencil 최적화 |
|
||||
| 웹 (> 1024px) | 700px × 180px | 마우스/터치패드 | 서명패드 USB 지원 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 기술 아키텍처
|
||||
|
||||
### 3.1 전체 구조
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ 클라이언트 (브라우저) │
|
||||
│ │
|
||||
│ ┌────────────────┐ ┌──────────────────────────────┐ │
|
||||
│ │ 필기 캔버스 │ │ 인식 엔진 (선택) │ │
|
||||
│ │ (Canvas API) │ │ │ │
|
||||
│ │ │ │ A. 클라우드 API (권장) │ │
|
||||
│ │ - 터치 이벤트 │ │ Google Cloud Vision │ │
|
||||
│ │ - 스트로크 수집│──→│ or Naver Clova OCR │ │
|
||||
│ │ - 이미지 생성 │ │ │ │
|
||||
│ │ │ │ B. 서버 사이드 │ │
|
||||
│ └────────────────┘ │ Tesseract OCR │ │
|
||||
│ └──────────────┬───────────────┘ │
|
||||
│ │ │
|
||||
└──────────────────────────────────────┼───────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ MNG 서버 (Laravel) │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ HandwritingVerificationService │ │
|
||||
│ │ │ │
|
||||
│ │ - verifyHandwriting(image, expectedText) │ │
|
||||
│ │ - calculateSimilarity(recognized, expected) │ │
|
||||
│ │ - saveVerificationResult(signerId, result) │ │
|
||||
│ │ - getVerificationHistory(contractId) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌──────────────────────────────────────────────────┐ │
|
||||
│ │ HWR 어댑터 (전략 패턴) │ │
|
||||
│ │ │ │
|
||||
│ │ interface HwrAdapter { │ │
|
||||
│ │ recognize(image): RecognitionResult │ │
|
||||
│ │ } │ │
|
||||
│ │ │ │
|
||||
│ │ ├─ GoogleVisionAdapter (권장) │ │
|
||||
│ │ ├─ NaverClovaAdapter (국내 대안) │ │
|
||||
│ │ └─ TesseractAdapter (무료 폴백) │ │
|
||||
│ └──────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 3.2 HWR(Handwriting Recognition) 엔진 비교
|
||||
|
||||
| 엔진 | 한글 인식 | 필기 인식 | 비용 | 속도 | 적합도 |
|
||||
|------|:--------:|:--------:|------|------|:------:|
|
||||
| **Google Cloud Vision** | ✅ 우수 | ✅ 우수 | $1.5/1000건 | ~1초 | ⭐⭐⭐ |
|
||||
| **Naver Clova OCR** | ✅ 최우수 | ✅ 우수 | 월 300건 무료 / $0.3~ | ~1초 | ⭐⭐⭐ |
|
||||
| **Kakao OCR** | ✅ 우수 | 🟡 보통 | 월 10,000건 무료 | ~1.5초 | ⭐⭐ |
|
||||
| **Tesseract 5** | 🟡 보통 | ❌ 약함 | 무료 | ~3초 | ⭐ |
|
||||
| **MyScript** | ✅ 우수 | ✅ 최우수 | 유료 (협의) | 실시간 | ⭐⭐⭐ |
|
||||
|
||||
**권장 조합**: Naver Clova OCR (1순위) + Google Vision (폴백)
|
||||
|
||||
- Naver Clova는 한국어 필기체 인식에 특화 (보험사 사용 실적)
|
||||
- 월 300건 무료 → SAM 전자계약 볼륨에 충분
|
||||
- Google Vision은 폴백 및 다국어 대응용
|
||||
|
||||
### 3.3 인식 프로세스
|
||||
|
||||
```
|
||||
[1] 캔버스 입력
|
||||
│
|
||||
├─ 스트로크 데이터 수집 (x, y, timestamp, pressure)
|
||||
├─ 캔버스 → PNG (base64)
|
||||
│
|
||||
▼
|
||||
[2] 전처리 (서버)
|
||||
│
|
||||
├─ 이미지 리사이즈 (너비 1200px 고정)
|
||||
├─ 이진화 (Otsu threshold)
|
||||
├─ 노이즈 제거 (가우시안 블러)
|
||||
├─ 기울기 보정 (deskew)
|
||||
│
|
||||
▼
|
||||
[3] HWR 엔진 호출
|
||||
│
|
||||
├─ Naver Clova: POST /custom/v1/general
|
||||
│ body: { images: [{ format: "png", data: base64 }] }
|
||||
│
|
||||
├─ Google Vision: POST /v1/images:annotate
|
||||
│ body: { requests: [{ image: { content: base64 }, features: [{ type: "TEXT_DETECTION" }] }] }
|
||||
│
|
||||
▼
|
||||
[4] 텍스트 유사도 계산
|
||||
│
|
||||
├─ 정규화: 공백 제거, 특수문자 제거
|
||||
├─ Levenshtein Distance (편집 거리)
|
||||
├─ Jaro-Winkler Similarity (부분 일치 가중)
|
||||
├─ 최종 일치율 = (Levenshtein 50% + Jaro-Winkler 50%)
|
||||
│
|
||||
▼
|
||||
[5] 판정
|
||||
│
|
||||
├─ 80% 이상: ✅ 통과 → 다음 단계
|
||||
├─ 60~79%: ⚠️ 재시도 권유 (힌트 제공)
|
||||
└─ 60% 미만: ❌ 재작성 필요
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 데이터 모델
|
||||
|
||||
### 4.1 신규 테이블: `esign_handwriting_verifications`
|
||||
|
||||
```sql
|
||||
CREATE TABLE esign_handwriting_verifications (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
contract_id BIGINT UNSIGNED NOT NULL,
|
||||
signer_id BIGINT UNSIGNED NOT NULL,
|
||||
|
||||
-- 확인 항목 정보
|
||||
step_order TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
prompt_text VARCHAR(200) NOT NULL, -- 요구 문구
|
||||
|
||||
-- 인식 결과
|
||||
recognized_text VARCHAR(500) NULL, -- HWR 인식 결과
|
||||
similarity_score DECIMAL(5,2) NULL, -- 일치율 (0.00~100.00)
|
||||
is_passed BOOLEAN NOT NULL DEFAULT FALSE, -- 통과 여부
|
||||
|
||||
-- 이미지 저장
|
||||
handwriting_image VARCHAR(500) NULL, -- 자필 이미지 경로
|
||||
|
||||
-- 인식 메타데이터
|
||||
hwr_engine VARCHAR(50) NULL, -- 사용 엔진 (clova/google/tesseract)
|
||||
hwr_confidence DECIMAL(5,2) NULL, -- 엔진 자체 신뢰도
|
||||
hwr_raw_response JSON NULL, -- 엔진 원본 응답
|
||||
|
||||
-- 시도 정보
|
||||
attempt_number TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
|
||||
-- 기록
|
||||
verified_at TIMESTAMP NULL, -- 통과 시각
|
||||
ip_address VARCHAR(45) NULL,
|
||||
user_agent VARCHAR(500) NULL,
|
||||
|
||||
created_at TIMESTAMP NULL,
|
||||
updated_at TIMESTAMP NULL,
|
||||
|
||||
INDEX idx_contract_signer (contract_id, signer_id),
|
||||
INDEX idx_tenant (tenant_id),
|
||||
|
||||
FOREIGN KEY (contract_id) REFERENCES esign_contracts(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (signer_id) REFERENCES esign_signers(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### 4.2 신규 테이블: `esign_verification_templates`
|
||||
|
||||
관리자가 계약 유형별로 확인 문구를 설정하는 템플릿이다.
|
||||
|
||||
```sql
|
||||
CREATE TABLE esign_verification_templates (
|
||||
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
|
||||
tenant_id BIGINT UNSIGNED NOT NULL,
|
||||
|
||||
name VARCHAR(100) NOT NULL, -- 템플릿명 (예: "표준 확인 문구")
|
||||
category VARCHAR(50) NULL, -- 카테고리 (근로계약, 영업계약 등)
|
||||
|
||||
-- 확인 단계 (JSON 배열)
|
||||
steps JSON NOT NULL,
|
||||
-- [
|
||||
-- { "order": 1, "text": "본인은 위 내용을 확인하였습니다", "threshold": 80 },
|
||||
-- { "order": 2, "text": "이름: {signer_name}", "threshold": 85 }
|
||||
-- ]
|
||||
|
||||
pass_threshold DECIMAL(5,2) NOT NULL DEFAULT 80.00, -- 기본 통과 임계값
|
||||
max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 5, -- 최대 시도 횟수
|
||||
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_by BIGINT UNSIGNED NULL,
|
||||
created_at TIMESTAMP NULL,
|
||||
updated_at TIMESTAMP NULL,
|
||||
|
||||
INDEX idx_tenant_active (tenant_id, is_active)
|
||||
);
|
||||
```
|
||||
|
||||
### 4.3 기존 테이블 변경
|
||||
|
||||
#### `esign_contracts` 추가 컬럼
|
||||
|
||||
```sql
|
||||
ALTER TABLE esign_contracts
|
||||
ADD COLUMN verification_template_id BIGINT UNSIGNED NULL AFTER completion_template_name,
|
||||
ADD COLUMN verification_required BOOLEAN NOT NULL DEFAULT FALSE AFTER verification_template_id;
|
||||
```
|
||||
|
||||
#### `esign_signers` 추가 컬럼
|
||||
|
||||
```sql
|
||||
ALTER TABLE esign_signers
|
||||
ADD COLUMN verification_status ENUM('pending', 'passed', 'failed', 'skipped')
|
||||
NOT NULL DEFAULT 'pending' AFTER status,
|
||||
ADD COLUMN verification_passed_at TIMESTAMP NULL AFTER consent_agreed_at;
|
||||
```
|
||||
|
||||
### 4.4 ERD 관계
|
||||
|
||||
```
|
||||
esign_contracts (1) ──── (*) esign_signers
|
||||
│ │
|
||||
│ │
|
||||
├── verification_ ├── (*) esign_handwriting_verifications
|
||||
│ template_id ──→ │ (시도별 1건, 통과까지 반복)
|
||||
│ │
|
||||
│ └── verification_status
|
||||
│ verification_passed_at
|
||||
│
|
||||
└──→ esign_verification_templates (1)
|
||||
└── steps: JSON [{ order, text, threshold }]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. API 설계
|
||||
|
||||
### 5.1 관리자 API (인증 필요)
|
||||
|
||||
#### 확인 템플릿 관리
|
||||
|
||||
| HTTP | URI | 설명 |
|
||||
|------|-----|------|
|
||||
| GET | `/esign/verification-templates` | 템플릿 목록 |
|
||||
| POST | `/esign/verification-templates` | 템플릿 생성 |
|
||||
| PUT | `/esign/verification-templates/{id}` | 템플릿 수정 |
|
||||
| DELETE | `/esign/verification-templates/{id}` | 템플릿 삭제 |
|
||||
|
||||
#### 계약 생성 시 (기존 확장)
|
||||
|
||||
```json
|
||||
// POST /esign/contracts/store (기존 파라미터 + 추가)
|
||||
{
|
||||
"title": "근로계약서",
|
||||
"verification_required": true,
|
||||
"verification_template_id": 1
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 서명자 API (토큰 기반, 비인증)
|
||||
|
||||
| HTTP | URI | 설명 |
|
||||
|------|-----|------|
|
||||
| GET | `/esign/sign/{token}/api/verification` | 확인 단계 정보 조회 |
|
||||
| POST | `/esign/sign/{token}/api/verification/submit` | 필기 인식 제출 |
|
||||
| GET | `/esign/sign/{token}/api/verification/status` | 진행 상태 확인 |
|
||||
|
||||
#### 확인 단계 조회 응답
|
||||
|
||||
```json
|
||||
// GET /esign/sign/{token}/api/verification
|
||||
{
|
||||
"verification_required": true,
|
||||
"steps": [
|
||||
{
|
||||
"order": 1,
|
||||
"prompt_text": "본인은 위 내용을 확인하였습니다",
|
||||
"status": "pending",
|
||||
"attempts_remaining": 5
|
||||
},
|
||||
{
|
||||
"order": 2,
|
||||
"prompt_text": "홍길동",
|
||||
"status": "pending",
|
||||
"attempts_remaining": 5
|
||||
}
|
||||
],
|
||||
"current_step": 1,
|
||||
"all_passed": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 필기 제출 요청/응답
|
||||
|
||||
```json
|
||||
// POST /esign/sign/{token}/api/verification/submit
|
||||
// Request
|
||||
{
|
||||
"step_order": 1,
|
||||
"handwriting_image": "data:image/png;base64,iVBOR..."
|
||||
}
|
||||
|
||||
// Response (성공)
|
||||
{
|
||||
"recognized_text": "본인은 위 내용을 확인하였습니다",
|
||||
"similarity_score": 92.5,
|
||||
"is_passed": true,
|
||||
"next_step": 2,
|
||||
"message": "확인이 완료되었습니다."
|
||||
}
|
||||
|
||||
// Response (실패)
|
||||
{
|
||||
"recognized_text": "본인은 위 내을 확합니다",
|
||||
"similarity_score": 54.3,
|
||||
"is_passed": false,
|
||||
"attempts_remaining": 4,
|
||||
"hints": [
|
||||
"글자 사이 간격을 조금 넓혀 보세요.",
|
||||
"또박또박 쓰면 인식률이 높아집니다."
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 서비스 계층 설계
|
||||
|
||||
### 6.1 신규 서비스
|
||||
|
||||
```
|
||||
app/Services/ESign/
|
||||
├── HandwritingVerificationService.php ← 메인 서비스
|
||||
├── HwrAdapterInterface.php ← 인식 엔진 인터페이스
|
||||
├── Adapters/
|
||||
│ ├── NaverClovaAdapter.php ← Naver Clova OCR
|
||||
│ ├── GoogleVisionAdapter.php ← Google Cloud Vision
|
||||
│ └── TesseractAdapter.php ← 무료 폴백
|
||||
└── TextSimilarityService.php ← 문자열 유사도 계산
|
||||
```
|
||||
|
||||
### 6.2 HandwritingVerificationService 주요 메서드
|
||||
|
||||
```php
|
||||
class HandwritingVerificationService
|
||||
{
|
||||
// 확인 단계 정보 조회
|
||||
public function getVerificationSteps(EsignSigner $signer): array
|
||||
|
||||
// 필기 이미지 제출 → 인식 → 유사도 검증
|
||||
public function submitHandwriting(
|
||||
EsignSigner $signer,
|
||||
int $stepOrder,
|
||||
string $base64Image
|
||||
): VerificationResult
|
||||
|
||||
// 모든 단계 통과 여부 확인
|
||||
public function isAllStepsPassed(EsignSigner $signer): bool
|
||||
|
||||
// 이미지 전처리 (리사이즈, 이진화, 노이즈 제거)
|
||||
private function preprocessImage(string $base64Image): string
|
||||
|
||||
// HWR 엔진 호출 (전략 패턴, 폴백 포함)
|
||||
private function recognizeText(string $processedImage): RecognitionResult
|
||||
|
||||
// 유사도 검증
|
||||
private function verifySimilarity(
|
||||
string $recognized,
|
||||
string $expected,
|
||||
float $threshold
|
||||
): bool
|
||||
}
|
||||
```
|
||||
|
||||
### 6.3 HwrAdapterInterface
|
||||
|
||||
```php
|
||||
interface HwrAdapterInterface
|
||||
{
|
||||
/**
|
||||
* 필기 이미지에서 텍스트를 인식한다.
|
||||
*
|
||||
* @param string $base64Image base64 인코딩 이미지
|
||||
* @return RecognitionResult { text, confidence, rawResponse }
|
||||
*/
|
||||
public function recognize(string $base64Image): RecognitionResult;
|
||||
|
||||
/**
|
||||
* 엔진 사용 가능 여부 확인
|
||||
*/
|
||||
public function isAvailable(): bool;
|
||||
}
|
||||
```
|
||||
|
||||
### 6.4 TextSimilarityService
|
||||
|
||||
```php
|
||||
class TextSimilarityService
|
||||
{
|
||||
/**
|
||||
* 두 문자열의 유사도를 계산한다 (0.0 ~ 100.0)
|
||||
*
|
||||
* 알고리즘:
|
||||
* 1. 정규화 (공백 제거, 특수문자 제거)
|
||||
* 2. Levenshtein Distance → 유사도 (50% 가중)
|
||||
* 3. Jaro-Winkler Similarity (50% 가중)
|
||||
* 4. 최종 점수 반환
|
||||
*/
|
||||
public function calculate(string $recognized, string $expected): float
|
||||
|
||||
/**
|
||||
* 정규화: 공백/특수문자 제거, 유니코드 정규화
|
||||
*/
|
||||
private function normalize(string $text): string
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 프론트엔드 설계
|
||||
|
||||
### 7.1 신규 뷰 파일
|
||||
|
||||
```
|
||||
resources/views/esign/sign/
|
||||
├── auth.blade.php (기존)
|
||||
├── sign.blade.php (기존)
|
||||
├── verification.blade.php (NEW — 필기 확인 페이지)
|
||||
└── done.blade.php (기존)
|
||||
```
|
||||
|
||||
### 7.2 필기 캔버스 구현
|
||||
|
||||
기존 `signature_pad` 대신 **필기 전용 캔버스**를 구현한다. 차이점:
|
||||
|
||||
| 항목 | 서명 캔버스 (v1) | 필기 캔버스 (고도화) |
|
||||
|------|----------------|-------------------|
|
||||
| 입력 영역 | 작은 박스 (서명용) | 넓은 영역 (문장 필기용) |
|
||||
| 가이드 | 없음 | 연한 가이드 문구 표시 |
|
||||
| 줄 표시 | 없음 | 필기 줄(lined) 가이드 |
|
||||
| 결과 | 이미지 저장만 | 인식 → 검증 → 결과 표시 |
|
||||
| 라이브러리 | signature_pad | Canvas API 직접 구현 |
|
||||
|
||||
### 7.3 JavaScript 모듈
|
||||
|
||||
```javascript
|
||||
// HandwritingCanvas — 필기 전용 캔버스 클래스
|
||||
class HandwritingCanvas {
|
||||
constructor(canvasElement, options = {}) {
|
||||
this.canvas = canvasElement;
|
||||
this.ctx = canvasElement.getContext('2d');
|
||||
this.strokes = []; // 스트로크 데이터
|
||||
this.isDrawing = false;
|
||||
this.lineWidth = options.lineWidth || 3;
|
||||
this.guideText = options.guideText || '';
|
||||
}
|
||||
|
||||
// 가이드 문구를 연한 색으로 표시
|
||||
drawGuide() {}
|
||||
|
||||
// 터치/마우스 이벤트 바인딩
|
||||
bindEvents() {}
|
||||
|
||||
// 캔버스 → base64 PNG
|
||||
toDataURL() {}
|
||||
|
||||
// 스트로크 데이터 (인식 보조용)
|
||||
getStrokeData() {}
|
||||
|
||||
// 초기화
|
||||
clear() {}
|
||||
|
||||
// 빈 캔버스 여부
|
||||
isEmpty() {}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 비즈니스 규칙
|
||||
|
||||
### 8.1 기본 확인 문구 (Default Prompts)
|
||||
|
||||
| 순번 | 문구 | 용도 | 임계값 |
|
||||
|------|------|------|:------:|
|
||||
| 1 | "본인은 위 내용을 확인하였습니다" | 범용 (모든 계약) | 80% |
|
||||
| 2 | "{서명자 이름}" | 본인 확인 강화 | 85% |
|
||||
| 3 | "위 계약 내용에 동의합니다" | 동의 확인 | 80% |
|
||||
|
||||
### 8.2 계약 유형별 기본 템플릿
|
||||
|
||||
| 계약 유형 | 확인 단계 | 문구 |
|
||||
|----------|:--------:|------|
|
||||
| 영업파트너 계약서 | 2단계 | ①내용 확인 + ②본인 이름 |
|
||||
| 근로계약서 | 2단계 | ①내용 확인 + ②본인 이름 |
|
||||
| 비밀유지 서약서 | 3단계 | ①내용 확인 + ②기밀 유지 확인 + ③본인 이름 |
|
||||
| 일반 계약서 | 1단계 | ①내용 확인 |
|
||||
|
||||
### 8.3 검증 규칙
|
||||
|
||||
| 규칙 | 값 | 비고 |
|
||||
|------|-----|------|
|
||||
| 기본 통과 임계값 | 80% | 관리자 조정 가능 (60~95%) |
|
||||
| 최대 시도 횟수 | 5회/단계 | 초과 시 관리자 확인 필요 |
|
||||
| 캔버스 최소 입력 | 5 스트로크 | 의미 있는 필기 보장 |
|
||||
| 이미지 최소 크기 | 200×50px | 인식 품질 보장 |
|
||||
| 인식 제한 시간 | 5초 | 타임아웃 시 재시도 |
|
||||
| 필기 확인 선택 여부 | 계약별 on/off | 기존 계약은 영향 없음 |
|
||||
|
||||
### 8.4 감사 로그 추가 액션
|
||||
|
||||
```
|
||||
verification_started — 필기 확인 단계 진입
|
||||
verification_attempted — 필기 제출 (결과 포함)
|
||||
verification_passed — 단계 통과
|
||||
verification_failed — 시도 횟수 초과
|
||||
verification_completed — 모든 단계 통과
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 보안 고려사항
|
||||
|
||||
| 위협 | 대응 |
|
||||
|------|------|
|
||||
| 이미지 조작 (외부 텍스트 이미지 촬영) | 스트로크 데이터 + 이미지 동시 검증, 스트로크 없는 이미지 거부 |
|
||||
| 동일 이미지 재사용 | 이미지 해시 중복 검사, 스트로크 타임스탬프 검증 |
|
||||
| 봇/자동화 공격 | 시도 횟수 제한, reCAPTCHA (선택), 필기 속도 패턴 분석 |
|
||||
| HWR API 장애 | 다중 엔진 폴백, 장애 시 수동 검증 모드 전환 |
|
||||
| 개인정보 | 자필 이미지 암호화 저장, 보관 기간 후 자동 삭제 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 설정 (config)
|
||||
|
||||
```php
|
||||
// config/esign.php (추가)
|
||||
'handwriting_verification' => [
|
||||
'enabled' => env('ESIGN_HWR_ENABLED', false),
|
||||
|
||||
// HWR 엔진 우선순위
|
||||
'engine' => env('ESIGN_HWR_ENGINE', 'clova'), // clova, google, tesseract
|
||||
'fallback_engine' => env('ESIGN_HWR_FALLBACK', 'google'),
|
||||
|
||||
// Naver Clova OCR
|
||||
'clova' => [
|
||||
'api_url' => env('CLOVA_OCR_API_URL'),
|
||||
'secret_key' => env('CLOVA_OCR_SECRET_KEY'),
|
||||
],
|
||||
|
||||
// Google Cloud Vision
|
||||
'google' => [
|
||||
'credentials_path' => env('GOOGLE_VISION_CREDENTIALS'),
|
||||
],
|
||||
|
||||
// 검증 설정
|
||||
'default_threshold' => 80.0, // 기본 통과 임계값 (%)
|
||||
'max_attempts' => 5, // 최대 시도 횟수
|
||||
'min_strokes' => 5, // 최소 스트로크 수
|
||||
'recognition_timeout' => 5, // 인식 타임아웃 (초)
|
||||
|
||||
// 이미지 전처리
|
||||
'image_max_width' => 1200,
|
||||
'image_format' => 'png',
|
||||
],
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 개발 로드맵
|
||||
|
||||
### Phase 1: 핵심 엔진 (2~3주)
|
||||
|
||||
| 작업 | 담당 | 설명 |
|
||||
|------|------|------|
|
||||
| DB 마이그레이션 | MNG | 신규 2테이블 + 기존 2테이블 변경 |
|
||||
| HWR 어댑터 구현 | MNG | Naver Clova + Google Vision 어댑터 |
|
||||
| TextSimilarityService | MNG | Levenshtein + Jaro-Winkler 유사도 |
|
||||
| HandwritingVerificationService | MNG | 메인 비즈니스 로직 |
|
||||
| 이미지 전처리 | MNG | GD/Imagick 기반 전처리 파이프라인 |
|
||||
|
||||
### Phase 2: 프론트엔드 (1~2주)
|
||||
|
||||
| 작업 | 담당 | 설명 |
|
||||
|------|------|------|
|
||||
| HandwritingCanvas 클래스 | MNG | 필기 전용 캔버스 (터치/마우스) |
|
||||
| verification.blade.php | MNG | 필기 확인 페이지 UI |
|
||||
| 디바이스 최적화 | MNG | 모바일/태블릿/웹 반응형 |
|
||||
| 인식 결과 UI | MNG | 일치율 표시, 재시도 안내 |
|
||||
|
||||
### Phase 3: 관리자 기능 (1주)
|
||||
|
||||
| 작업 | 담당 | 설명 |
|
||||
|------|------|------|
|
||||
| 확인 템플릿 관리 UI | MNG | 문구 설정, 임계값 조정 |
|
||||
| 계약 생성 마법사 연동 | MNG | 필기 확인 on/off 토글 |
|
||||
| 감사 로그 연동 | MNG | 시도 이력 조회 |
|
||||
|
||||
### Phase 4: 검증 및 안정화 (1주)
|
||||
|
||||
| 작업 | 담당 | 설명 |
|
||||
|------|------|------|
|
||||
| 인식률 테스트 | R&D | 다양한 필체로 인식률 검증 |
|
||||
| 임계값 튜닝 | R&D | 실 데이터 기반 최적 임계값 |
|
||||
| 폴백 시나리오 테스트 | MNG | API 장애, 타임아웃 처리 |
|
||||
| 보안 테스트 | MNG | 이미지 조작, 재사용 방지 |
|
||||
|
||||
### 총 예상 기간: 5~7주
|
||||
|
||||
---
|
||||
|
||||
## 12. 비용 추정
|
||||
|
||||
### HWR API 비용 (월간)
|
||||
|
||||
| 엔진 | 무료 한도 | 초과 단가 | 예상 월 사용량 | 예상 비용 |
|
||||
|------|----------|----------|:------------:|----------|
|
||||
| Naver Clova | 300건/월 | ~$0.3/건 | 100~200건 | **무료** |
|
||||
| Google Vision | 1,000건/월 | $1.5/1,000건 | 폴백 전용 | **무료~$1.5** |
|
||||
|
||||
> **참고**: 전자계약 월 50건 × 평균 2단계 × 평균 2회 시도 = ~200건/월 → 무료 범위 내
|
||||
|
||||
---
|
||||
|
||||
## 13. 기존 v1과의 공존 전략
|
||||
|
||||
| 항목 | 정책 |
|
||||
|------|------|
|
||||
| 기존 계약 | 영향 없음 (verification_required = false) |
|
||||
| 새 계약 | 관리자가 선택 (필기 확인 on/off) |
|
||||
| 서명 단계 | 필기 확인 통과 후 기존 서명/도장 단계로 진입 |
|
||||
| 라우팅 | `/esign/sign/{token}` → auth → **verification** (NEW) → sign → done |
|
||||
| 코드 구조 | 기존 EsignPublicController에 verification 메서드 추가 (별도 서비스 분리) |
|
||||
| 뷰 파일 | 신규 verification.blade.php 추가 (기존 파일 수정 최소화) |
|
||||
|
||||
---
|
||||
|
||||
## 관련 문서
|
||||
|
||||
- [전자서명 기능 개요](README.md)
|
||||
- [전자서명 기술 설계 (v1)](../../projects/e-sign/technical-design.md)
|
||||
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md)
|
||||
- [알림톡 연동 가이드](../barobill-kakaotalk/esign-notification-guide.md)
|
||||
|
||||
---
|
||||
|
||||
**최종 업데이트**: 2026-03-22
|
||||
Reference in New Issue
Block a user