- API Key와 API 활성화는 별개 개념 (인증 vs 서비스 사용 권한) - 활성화 직접 링크 및 CLI 방법 추가 - SAM 프로젝트 Google API 활성화 목록 (Vertex AI, GCS, FCM, Vision) - GoogleVisionAdapter 인증 우선순위 3단계 설명
35 KiB
전자계약 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', 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 활성화 = 두 가지 모두 필요
활성화 방법:
- Google Cloud Console 접속
- 프로젝트 선택:
codebridge-chatbot(ID: 814841800268) - [API 및 서비스] → [라이브러리] → "Cloud Vision API" 검색
- "사용(Enable)" 버튼 클릭
- 활성화 후 수 분 내 적용
직접 링크:
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 키 없을 시 빈 결과 + 적절한 힌트 반환 |
남은 작업 (추후)
| 작업 | 우선순위 | 설명 |
|---|---|---|
| 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