Files
sam-docs/features/esign/handwriting-verification.md

33 KiB
Raw Blame History

전자계약 eSign 고도화 — 필기 문구 확인 (Handwriting Verification)

작성일: 2026-03-22 상태: Phase 1~3 구현 완료 (관리자 기능 + 데모) 담당: R&D실 메뉴: 연구개발 > 전자서명 고도화 (/esign-verification) v1 참조: features/esign/README.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

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

관리자가 계약 유형별로 확인 문구를 설정하는 템플릿이다.

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 추가 컬럼

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 추가 컬럼

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} 템플릿 삭제

계약 생성 시 (기존 확장)

// 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 진행 상태 확인

확인 단계 조회 응답

// 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
}

필기 제출 요청/응답

// 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 주요 메서드

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

interface HwrAdapterInterface
{
    /**
     * 필기 이미지에서 텍스트를 인식한다.
     *
     * @param string $base64Image base64 인코딩 이미지
     * @return RecognitionResult { text, confidence, rawResponse }
     */
    public function recognize(string $base64Image): RecognitionResult;

    /**
     * 엔진 사용 가능 여부 확인
     */
    public function isAvailable(): bool;
}

6.4 TextSimilarityService

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 모듈

// 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)

// 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)

# 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_* 테이블 미수정

관련 문서


최종 업데이트: 2026-03-22