Appearance
연동 개요 및 용어 정의
모바일 신분증 연동을 위한 개념, 용어, 기본 Handler 경로, 인코딩 방식 등을 설명합니다.
Default handler 경로
| 종류 | Handler 경로 | 예시 URL |
|---|---|---|
| 프로필 조회 | /mip/profile | http://example.com/mip/profile |
| 이미지 조회 | /mip/image | http://example.com/mip/image |
| VP 제출 | /mip/vp | http://example.com/mip/vp |
| 오류 제출 | /mip/error | http://example.com/mip/error |
1. 개요
서비스 제공자(SP, Service Provider) 연계를 위해 필요한 용어와 인터페이스, 데이터 전송 방식에 대해 설명해요.
INFO
- mDL (모바일 운전면허증)
모바일로 발급한 운전면허증이에요. - 모바일 신분증
국가에서 발급한 신분증을 스마트폰 앱 형태로 제공한 것이에요. - DID (탈중앙화 신원, Decentralized ID)
중앙기관 없이 신원을 관리할 수 있는 방식이에요. - DID 문서
DID와 연결된 정보(ID, 공개키 등)를 담고 있으며, 블록체인에 등록돼 누구나 조회할 수 있어요.
W3C의 Decentralized Identifier v1.0과 Verifiable Credential Data Model v1.0을 따라요. - VC (검증 가능한 증명서, Verifiable Credential)
발급자(Issuer)가 소유자(Holder)에게 발급한 디지털 증명서예요. - VP (검증 가능한 제시 자료, Verifiable Presentation)
Holder가 하나 이상의 VC를 모아 서명한 문서로, 서비스 이용을 위해 검증자에게 제출해요. - Holder (소유자)
VC를 소유한 사람 또는 앱이에요. - Issuer (발급자)
VC를 발급하는 기관이나 앱이에요. - Verifier (검증자)
VP를 받아서 진위 여부를 확인하는 주체로, 보통 서비스 제공자(SP)가 맡아요.
인터페이스와 전송 방식
모바일 신분증을 사용하는 다양한 상황에 따라 인터페이스와 전송 방식을 선택할 수 있어요. 아래 표는 사용 시나리오에 따라 어떤 방식이 쓰이는지 요약한 거예요.
인터페이스 종류
| 방식 | 설명 | 참고 항목 |
|---|---|---|
| QR-MPM | 단말기(상점 등)에서 QR코드를 보여주고 신분증 앱이 스캔해요. | Merchant Presented Mode |
| QR-CPM | 신분증 앱에서 QR코드를 생성해 단말기가 스캔해요. | Customer Presented Mode |
| App2App | 신분증 앱과 서비스 앱이 URL 스킴으로 직접 통신해요. | |
| PUSH | 조폐공사 서버가 신분증 앱에 VP 제출을 요청해요. | |
| BLE + QR | QR코드로 BLE(Bluetooth Low Energy) 연결한 뒤 통신해요. | CPM, MPM 모두 사용 가능 |
| NFC | 신분증 앱이 단말기와 NFC 방식으로 직접 통신해요. |
전송 방식
| 모드 | 설명 | 특징 |
|---|---|---|
| direct | VP를 검증 서버에 직접 전송해요. | |
| indirect | App2App이나 BLE 방식으로 VP를 단말기나 서비스 앱에 전달해요. | |
| proxy | 검증 서버와 통신할 수 없을 때, 중계 서버를 통해 VP를 전송해요. | 웹소켓 사용 |
| nfc | 단말기와 NFC 방식으로 직접 VP를 전송해요. |
인코딩과 디코딩
데이터 전송 시 문자열을 URL-safe 형식으로 변환하려면 Base64 인코딩을 사용해요. 이때 RFC 4648에서 정의한 URL 및 파일 이름 안전 알파벳을 따르는 방식이에요.
Base64 (URL Safe)
- 참고 문서: RFC 4648, 5항
Node.js 예시
js
const urlsafe_base64 = require('urlsafe-base64');
const { Buffer } = require('buffer');
function base64Encode(str) {
return urlsafe_base64.encode(Buffer.from(str));
}
function base64Decode(base64) {
return urlsafe_base64.decode(base64).toString();
}
const base64 = base64Encode('Test string');
console.log(base64); // 인코딩 결과
console.log(base64Decode(base64)); // 디코딩 결과Java 8 예시
java
import java.util.Base64;
import static java.nio.charset.StandardCharsets.UTF_8;
String rawCookieValue = "(DMPD)b80f9ed8-4e66-41a4-ac60-b46ea5586cf0";
// URL-safe Base64 인코딩 (Padding 제거)
byte[] encodedBytes = Base64.getUrlEncoder()
.withoutPadding()
.encode(rawCookieValue.getBytes(UTF_8));
String encodedValue = new String(encodedBytes, UTF_8);
System.out.println(encodedValue);
// Node.js에서 인코딩한 결과와 비교
String encodedFromNodeJs = "KERNUEQpYjgwZjllZDgtNGU2Ni00MWE0LWFjNjAtYjQ2ZWE1NTg2Y2Yw";
System.out.println(encodedFromNodeJs);
System.out.println("Node.js와 Java 인코딩 결과 일치 여부: " + encodedValue.equals(encodedFromNodeJs));
// 디코딩
byte[] decodedBytes = Base64.getUrlDecoder().decode(encodedValue.getBytes(UTF_8));
String decodedValue = new String(decodedBytes, UTF_8);
// 검증
System.out.println("++++++++++++");
System.out.println(rawCookieValue);
System.out.println(decodedValue);
System.out.println("원본 문자열과 디코딩 결과 일치 여부: " + rawCookieValue.equals(decodedValue));