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

38 KiB
Raw Blame History

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

작성일: 2026-03-22 상태: 전체 구현 완료 (Phase 1~5) 담당: 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', true),

    // HWR 엔진 우선순위: clova, google_vision, tesseract
    'engine' => env('ESIGN_HWR_ENGINE', 'clova'),
    'fallback_engine' => env('ESIGN_HWR_FALLBACK', 'google_vision'),

    // Naver Clova OCR
    'clova' => [
        'api_url' => env('CLOVA_OCR_API_URL'),
        'secret_key' => env('CLOVA_OCR_SECRET_KEY'),
    ],

    // Google Cloud Vision (서비스 계정 우선, API Key 폴백)
    'google_vision' => [
        'credentials_path' => env('GOOGLE_VISION_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')),
        'api_key' => env('GOOGLE_VISION_API_KEY'),
    ],

    // 검증 설정
    'default_threshold' => 80.0,
    'max_attempts' => 5,
    'min_strokes' => 5,
    'recognition_timeout' => 5,

    // 이미지 전처리
    'image_max_width' => 1200,
    'image_format' => 'png',
],

10.1 Google Cloud Vision API 활성화 절차 (필수)

핵심: Google Cloud 서비스 계정(API Key)이 있더라도, 개별 API는 프로젝트 수준에서 별도로 활성화해야 한다. API Key/서비스 계정은 인증 수단이고, API 활성화는 서비스 사용 권한이다 — 별개의 개념이다.

❌ 서비스 계정만 있으면 모든 Google API를 바로 사용 가능 (틀림)
✅ 서비스 계정 + 해당 API 활성화 = 두 가지 모두 필요

활성화 방법:

  1. Google Cloud Console 접속
  2. 프로젝트 선택: codebridge-chatbot (ID: 814841800268)
  3. [API 및 서비스] → [라이브러리] → "Cloud Vision API" 검색
  4. "사용(Enable)" 버튼 클릭
  5. 활성화 후 수 분 내 적용

직접 링크:

https://console.developers.google.com/apis/api/vision.googleapis.com/overview?project=codebridge-chatbot

CLI 방법 (gcloud 설치 시):

gcloud services enable vision.googleapis.com --project=codebridge-chatbot

현재 SAM 프로젝트에서 활성화된 Google API 목록:

API 용도 상태
Vertex AI AI 기능 (Gemini) 기존
Cloud Storage 파일 저장 (GCS) 기존
FCM 푸시 알림 기존
Cloud Vision 필기 인식 (HWR) 2026-03-23 활성화

10.2 인증 우선순위 (GoogleVisionAdapter)

1순위: 서비스 계정 JSON → OAuth2 Bearer Token
       ├─ config('esign.handwriting_verification.google_vision.credentials_path')
       ├─ env('GOOGLE_APPLICATION_CREDENTIALS')
       └─ config('gcs.service_account_path')  ← 기존 GCS 서비스 계정 공유

2순위: API Key (폴백)
       └─ config('esign.handwriting_verification.google_vision.api_key')

SAM 프로젝트는 기존 GCS 서비스 계정(/var/www/sales/apikey/google_service_account.json)을 공유하므로 .env 변경 없이 Vision API를 사용한다.


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 키 없을 시 빈 결과 + 적절한 힌트 반환

Phase 5: 추가 고도화 — 구현 완료 (2026-03-23)

작업 상태 내용
Google Vision API 활성화 프로젝트 codebridge-chatbot에서 Vision API 활성화 (서비스 계정 공유)
실제 서명 플로우 연동 OTP → 필기 확인 → 서명/도장 (sign/verification.blade.php)
esign_contracts 컬럼 verification_required, verification_template_id 추가
esign_signers 컬럼 verification_status, verification_passed_at 추가
계약 생성 마법사 연동 "필기 문구 확인" 체크박스 + 템플릿 선택 드롭다운
감사 로그 연동 verification_viewed, _attempted, _passed, _completed 4개 액션
시험 테스트 모달 템플릿 목록에서 단계별 필기 인식 테스트 모달
토큰 사용량 누계 엔진별(Clova/Vision/Tesseract) 호출 횟수 + 대시보드 카드
캔버스 좌표 보정 CSS 스케일링 시 마우스 좌표 scaleX/scaleY 보정

남은 작업 (추후)

작업 우선순위 설명
Naver Clova OCR 연동 낮음 1순위 엔진으로 설정 시 .env에 키 추가 (현재 Google Vision으로 충분)
인식률 실 데이터 튜닝 낮음 다양한 필체 테스트 후 임계값 최적화

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
│   ├── 2026_03_22_200000_add_verification_to_esign_contracts_table.php
│   └── 2026_03_22_210000_add_verification_to_esign_signers_table.php
├── app/Models/ESign/
│   ├── EsignVerificationTemplate.php                             ← NEW
│   ├── EsignHandwritingVerification.php                          ← NEW
│   ├── EsignContract.php                                         ← 수정 (fillable/casts/관계)
│   └── EsignSigner.php                                           ← 수정 (fillable/casts)
├── 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                           ← 관리 컨트롤러 (NEW)
│   ├── EsignPublicController.php                                 ← 수정 (필기 확인 API 추가)
│   └── EsignApiController.php                                    ← 수정 (store에 verification 필드)
├── resources/views/esign/
│   ├── verification/
│   │   ├── dashboard.blade.php                                   ← 대시보드 + 토큰 사용량
│   │   ├── templates.blade.php                                   ← 템플릿 CRUD + 시험 모달
│   │   └── demo.blade.php                                        ← 필기 인식 테스트
│   ├── sign/
│   │   └── verification.blade.php                                ← 공개 서명 필기 확인 (NEW)
│   ├── sign/auth.blade.php                                       ← 수정 (verification 리다이렉트)
│   └── create.blade.php                                          ← 수정 (필기 확인 토글)
└── routes/web.php                                                ← 수정 (14개 라우트 추가)

14.2 엔드포인트 (14개)

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 검증 이력
GET /esign/sign/{token}/verification 공개 필기 확인 페이지
GET /esign/sign/{token}/api/verification 필기 확인 단계 조회
POST /esign/sign/{token}/api/verification/submit 필기 인식 제출

14.3 HWR 엔진 현황

엔진 상태 인증 방식 비고
Google Vision 활성화 기존 GCS 서비스 계정 공유 .env 변경 없이 사용 가능
Naver Clova 대기 API Key 필요 .env에 키 추가 시 1순위 전환
Tesseract 미설치 서버 설치 필요 필기체 인식률 낮음

14.4 기존 서비스 영향

✅ 기존 계약 (verification_required=false): 영향 없음
✅ 관리자 기능: 별도 prefix (/esign-verification)
✅ 공개 서명 플로우: verification_required=true인 계약만 필기 확인 단계 추가
✅ 기존 서명/도장 기능: 변경 없음

관련 문서


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