859 lines
33 KiB
Markdown
859 lines
33 KiB
Markdown
# 전자계약 eSign 고도화 — 필기 문구 확인 (Handwriting Verification)
|
||
|
||
> **작성일**: 2026-03-22
|
||
> **상태**: Phase 1~3 구현 완료 (관리자 기능 + 데모)
|
||
> **담당**: R&D실
|
||
> **메뉴**: 연구개발 > 전자서명 고도화 (`/esign-verification`)
|
||
> **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: 핵심 엔진 — ✅ 구현 완료 (2026-03-22)
|
||
|
||
| 작업 | 상태 | 파일 |
|
||
|------|:----:|------|
|
||
| DB 마이그레이션 | ✅ | `mng/database/migrations/2026_03_22_1*` (2개) |
|
||
| 모델 | ✅ | `EsignVerificationTemplate`, `EsignHandwritingVerification` |
|
||
| HWR 어댑터 | ✅ | `NaverClovaAdapter`, `GoogleVisionAdapter`, `TesseractAdapter` |
|
||
| TextSimilarityService | ✅ | Levenshtein + 글자유사도 (한글 mb_str_split 안전) |
|
||
| HandwritingVerificationService | ✅ | 메인 비즈니스 로직 (제출/검증/폴백/힌트) |
|
||
| config/esign.php | ✅ | HWR 엔진/임계값/시도횟수 설정 |
|
||
|
||
### Phase 2: 프론트엔드 — ✅ 구현 완료 (2026-03-22)
|
||
|
||
| 작업 | 상태 | 파일 |
|
||
|------|:----:|------|
|
||
| HandwritingCanvas (React) | ✅ | Canvas API 직접 구현 (터치/마우스, 가이드 문구) |
|
||
| 인식 데모 페이지 | ✅ | `views/esign/verification/demo.blade.php` |
|
||
| 인식 결과 UI | ✅ | 일치율 프로그레스바, 힌트, 테스트 이력 테이블 |
|
||
|
||
### Phase 3: 관리자 기능 — ✅ 구현 완료 (2026-03-22)
|
||
|
||
| 작업 | 상태 | 파일 |
|
||
|------|:----:|------|
|
||
| 확인 템플릿 CRUD | ✅ | `views/esign/verification/templates.blade.php` (React) |
|
||
| 대시보드 | ✅ | `views/esign/verification/dashboard.blade.php` (통계/흐름도) |
|
||
| 컨트롤러 | ✅ | `EsignVerificationController` (11개 API) |
|
||
| 라우트 | ✅ | `/esign-verification/*` (11개 엔드포인트) |
|
||
| 메뉴 | ✅ | 연구개발 > 전자서명 고도화 (ID: 15621) |
|
||
|
||
### Phase 4: 검증 및 안정화 — ✅ 기본 검증 완료 (2026-03-22)
|
||
|
||
| 작업 | 상태 | 결과 |
|
||
|------|:----:|------|
|
||
| TextSimilarity 유사도 테스트 | ✅ | 완전일치 100%, 오타1개 92.9%, 오타2개 85.7%, 부분누락 64.3%, 완전다름 25% |
|
||
| 서비스 DI 검증 | ✅ | Laravel 컨테이너 자동 주입 정상 |
|
||
| 라우트 등록 검증 | ✅ | 11개 라우트 정상 |
|
||
| HWR 폴백 검증 | ✅ | API 키 없을 시 빈 결과 + 적절한 힌트 반환 |
|
||
|
||
### 남은 작업 (추후)
|
||
|
||
| 작업 | 우선순위 | 설명 |
|
||
|------|:-------:|------|
|
||
| HWR API 키 설정 | 높음 | `.env`에 Naver Clova 또는 Google Vision API 키 추가 |
|
||
| 실제 전자계약 연동 | 높음 | 기존 서명 플로우에 필기 확인 단계 삽입 (verification.blade.php) |
|
||
| `esign_contracts` 컬럼 추가 | 중간 | `verification_required`, `verification_template_id` |
|
||
| `esign_signers` 컬럼 추가 | 중간 | `verification_status`, `verification_passed_at` |
|
||
| 인식률 실 데이터 튜닝 | 중간 | 다양한 필체 테스트 후 임계값 조정 |
|
||
| 감사 로그 연동 | 낮음 | `esign_audit_logs`에 verification 액션 기록 |
|
||
|
||
---
|
||
|
||
## 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 추가 (기존 파일 수정 최소화) |
|
||
|
||
---
|
||
|
||
## 14. 구현 결과 (2026-03-22)
|
||
|
||
### 14.1 생성된 파일 목록
|
||
|
||
```
|
||
mng/
|
||
├── config/esign.php ← HWR 설정
|
||
├── database/migrations/
|
||
│ ├── 2026_03_22_100000_create_esign_verification_templates_table.php
|
||
│ └── 2026_03_22_110000_create_esign_handwriting_verifications_table.php
|
||
├── app/Models/ESign/
|
||
│ ├── EsignVerificationTemplate.php
|
||
│ └── EsignHandwritingVerification.php
|
||
├── app/Services/ESign/
|
||
│ ├── TextSimilarityService.php ← 유사도 계산
|
||
│ ├── HwrAdapterInterface.php ← 엔진 인터페이스
|
||
│ ├── HandwritingVerificationService.php ← 메인 서비스
|
||
│ └── Adapters/
|
||
│ ├── NaverClovaAdapter.php ← Naver Clova OCR
|
||
│ ├── GoogleVisionAdapter.php ← Google Vision
|
||
│ └── TesseractAdapter.php ← 무료 폴백
|
||
├── app/Http/Controllers/ESign/
|
||
│ └── EsignVerificationController.php ← 관리 컨트롤러
|
||
└── resources/views/esign/verification/
|
||
├── dashboard.blade.php ← 대시보드
|
||
├── templates.blade.php ← 템플릿 CRUD
|
||
└── demo.blade.php ← 필기 인식 테스트
|
||
```
|
||
|
||
### 14.2 엔드포인트 (11개)
|
||
|
||
| Method | URI | 설명 |
|
||
|--------|-----|------|
|
||
| GET | `/esign-verification` | 대시보드 |
|
||
| GET | `/esign-verification/templates` | 템플릿 관리 |
|
||
| GET | `/esign-verification/demo` | 인식 테스트 |
|
||
| GET | `/esign-verification/api/stats` | 통계 |
|
||
| GET | `/esign-verification/api/templates` | 템플릿 목록 |
|
||
| POST | `/esign-verification/api/templates` | 템플릿 생성 |
|
||
| GET | `/esign-verification/api/templates/{id}` | 템플릿 상세 |
|
||
| PUT | `/esign-verification/api/templates/{id}` | 템플릿 수정 |
|
||
| DELETE | `/esign-verification/api/templates/{id}` | 템플릿 삭제 |
|
||
| POST | `/esign-verification/api/demo` | 인식 테스트 API |
|
||
| GET | `/esign-verification/api/history` | 검증 이력 |
|
||
|
||
### 14.3 HWR 활성화 (.env)
|
||
|
||
```bash
|
||
# Naver Clova OCR (1순위 권장)
|
||
CLOVA_OCR_API_URL=https://xxxxxx.apigw.ntruss.com/custom/v1/xxxxx/general
|
||
CLOVA_OCR_SECRET_KEY=your_secret_key
|
||
|
||
# Google Vision (폴백)
|
||
GOOGLE_VISION_API_KEY=your_api_key
|
||
```
|
||
|
||
### 14.4 기존 서비스 영향
|
||
|
||
```
|
||
✅ 기존 eSign 파일 수정 0건
|
||
✅ 별도 prefix (/esign-verification) — 기존 /esign/* 라우트와 완전 독립
|
||
✅ 별도 컨트롤러 (EsignVerificationController) — 기존 컨트롤러 미수정
|
||
✅ 별도 테이블 (MNG 마이그레이션) — 기존 esign_* 테이블 미수정
|
||
```
|
||
|
||
---
|
||
|
||
## 관련 문서
|
||
|
||
- [전자서명 기능 개요](README.md)
|
||
- [전자서명 기술 설계 (v1)](../../projects/e-sign/technical-design.md)
|
||
- [DB 스키마 — 문서/전자서명](../../system/database/documents.md)
|
||
- [알림톡 연동 가이드](../barobill-kakaotalk/esign-notification-guide.md)
|
||
|
||
---
|
||
|
||
**최종 업데이트**: 2026-03-22
|