인쇄물 원본 확인
출력물의 QR 코드로 내 서버의 확인 페이지를 열어, 보관한 원본을 다시 그려 SHA-256 지문을 비교하고 워터마크를 넣은 사본을 보여 줍니다. 데이터는 내 서버 밖으로 나가지 않습니다.
출력물을 받은 사람이 QR 코드를 찍으면 진본인지, 바뀐 곳이 없는지 바로 확인합니다. 다른 리포팅 도구의 진위 확인 기능과 같지만 문서가 내 서버 밖으로 나가지 않습니다. 문서를 발급할 때 정의와 데이터(또는 둘의 스냅샷)를 문서번호로 내 DB에 보관하고, QR 코드에는 내 확인 페이지 주소와 그 번호, 확인코드를 넣습니다. 확인 페이지는 보관한 원본을 불러 다시 레이아웃하고 지문을 비교합니다. FreeReport로는 문서가 전송되지 않습니다. 재직증명서로 해 보기
// 1. issuing: the QR holds your verify address, the id and the check code
const definition = {
name: '재직증명서',
verifyUrl: 'https://erp.example.com/verify?id={params.docId}&h={docHashShort}',
params: { docId: { type: 'string' } },
bands: { title: { height: 250, elements: [
// …the certificate…
{ type: 'barcode', format: 'qr', x: 0, y: 226, w: 22, h: 22, expr: 'verifyUrl' },
{ type: 'text', x: 24, y: 232, w: 90, h: 8, text: '확인코드 {docHashShort}' },
] } },
};
const report = createReport(definition, data, { params: { docId } });
await report.ready();
// keep what you printed, on your own server
await db.insert('issued_documents', { id: docId, definition, data, params: { docId }, hash: report.fingerprint(), issuedAt: new Date() });// 2. https://erp.example.com/verify?id=…&h=… (your page)
import { createVerifierPage } from '@freereportjs/core';
import '@freereportjs/core/styles.css';
const q = new URLSearchParams(location.search);
createVerifierPage(document.getElementById('verify')!, {
id: q.get('id')!,
hash: q.get('h')!,
lang: 'ko',
load: async id => {
const r = await fetch('/api/issued-documents/' + encodeURIComponent(id));
return r.ok ? r.json() : null; // { definition, data, params, issuer, issuedAt }
},
});| 이름 | 타입 | 설명 |
|---|---|---|
verifyUrl | definition: string | QR 코드에 넣을 주소 템플릿. {수식}에서 데이터, params, docHash, docHashShort를 쓸 수 있고 값은 URL 인코딩됩니다. expr이 verifyUrl인 QR 코드로 찍습니다. |
verifyReport(definition, data, hash, opts?) | Promise<{ ok, hash, expected, pages }> | 보고서를 다시 레이아웃하고(브라우저에서는 웹 글꼴을 기다림) 지문을 hash와 비교합니다. hash는 64자 전체나 8자 이상의 앞부분(docHashShort). opts는 레이아웃 옵션(params, measure, locale). |
createVerifierPage(el, { load, id, hash, lang? }) | VerifierPage | 바로 쓰는 확인 화면: 내 서버에서 load(id)로 원본을 받아 "원본과 일치" 또는 "일치하지 않음"과 발급기관·발급일시를 보여 주고, 원본 쪽을 "검증용 사본" 워터마크를 넣어 읽기 전용으로 보여 줍니다(인쇄·저장 버튼 없음). done은 결과로 끝나고 destroy()로 지웁니다. |
load(id) | { definition, data?, params?, issuedAt?, issuer? } | null | 내가 만드는 함수: 그 번호로 보관한 정의와 데이터를 돌려줍니다(없으면 null). |
verifyReportServer(definition, data, hash, { fonts }) | Promise<VerifyResult> | 같은 확인을 Node.js, Deno, Bun에서 합니다. 글자는 renderPdf처럼 트루타입 글꼴로 잽니다. |
확인코드가 덮는 범위
지문은 모든 쪽에 인쇄된 글자(텍스트·필드 요소, 쪽 순서와 읽는 순서대로, 연속 공백은 하나로)의 SHA-256입니다. 워터마크, 바코드와 QR 코드, 이미지, 선, 사각형, 차트, 엠보싱, docHash나 verifyUrl을 쓰는 글자는 빠지므로 확인코드나 QR을 찍어도 지문이 바뀌지 않습니다. docHashShort는 앞 12자리입니다. 줄바꿈과 쪽 나눔도 지문에 들어가므로 발급할 때와 같은 글꼴로 확인하세요(브라우저 뷰어는 내장 웹 글꼴, 서버는 같은 TTF 파일로 verifyReportServer). 날짜는 now() 대신 데이터의 발급일을 찍어야 나중에 다시 그려도 같습니다.
// or check on the server (Node.js, Deno, Bun)
import { verifyReportServer } from '@freereportjs/core';
const doc = await db.get('issued_documents', id);
const { ok, hash } = await verifyReportServer(doc.definition, doc.data, h, { params: doc.params, fonts });확인 페이지는 원본 쪽을 문서의 워터마크 대신 "검증용 사본" 워터마크를 넣고 인쇄·저장 버튼 없이 보여 주므로 사본이 원본 행세를 할 수 없습니다. 문서를 확인할 수 있어야 하는 기간 동안 보관본을 지우지 마세요. 보관본 없이 확인코드만으로는 아무것도 증명하지 못합니다.