Laravel 기반 서비스에 NHN KCP 휴대폰 본인인증을 통합했다. KCP 개발 문서가 방대하고 PHP 샘플이 오래되어 정리가 필요했다.
Table of contents
Open Table of contents
전체 흐름
사용자 → [본인인증 요청] → Laravel (폼 생성)
→ [cert.kcp.co.kr] → KCP 인증 화면 (OTP 입력)
→ [인증 완료] → Laravel 콜백 (/api/identification/result)
→ [결과 저장] → identifications 테이블
핵심은 KCP가 인증 과정을 전부 처리하고, 우리 서버는 요청 생성과 결과 수신만 담당한다는 점이다.
설정
// config/identification.php
return [
'kcp' => [
'site_cd' => env('IDENTIFICATION_KCP_SITE_CD'),
'site_id' => env('IDENTIFICATION_KCP_SITE_ID'),
]
];
site_cd는 가맹점 코드, site_id는 웹사이트 식별자다. .env에서 관리하고 코드에 하드코딩하지 않는다.
요청 폼
KCP 인증은 브라우저에서 직접 KCP 서버로 POST하는 방식이다. 서버 사이드에서 API를 호출하는 것이 아니라, HTML 폼을 렌더링하여 사용자 브라우저가 KCP로 이동한다.
<!-- Blade 템플릿 -->
<form name="form_auth" method="post"
action="https://cert.kcp.co.kr/kcp_cert/cert_view.jsp">
<input type="hidden" name="site_cd" value="{{ $siteCd }}">
<input type="hidden" name="req_tx" value="cert">
<input type="hidden" name="cert_method" value="01"> <!-- 휴대폰 -->
<input type="hidden" name="cert_otp_use" value="Y">
<input type="hidden" name="cert_enc_use" value="Y">
<input type="hidden" name="ordr_idxx" value="{{ $orderId }}">
<input type="hidden" name="web_siteid" value="{{ $siteId }}">
<input type="hidden" name="up_hash" value="{{ $upHash }}">
<input type="hidden" name="Ret_URL" value="{{ $callbackUrl }}">
</form>
<script>document.form_auth.submit();</script>
up_hash는 요청 무결성 검증용 해시값으로, 서버에서 생성한다.
데이터베이스 설계
요청과 결과를 분리하는 2-테이블 패턴을 사용했다.
identification_requests
인증 요청 시점의 메타데이터를 기록한다.
CREATE TABLE identification_requests (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
ip VARCHAR(45) NOT NULL, -- 클라이언트 IP
agent TEXT NOT NULL, -- User-Agent
created_at TIMESTAMP,
updated_at TIMESTAMP
);
identifications
KCP에서 반환한 인증 결과를 저장한다.
CREATE TABLE identifications (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
request_id BIGINT UNSIGNED NOT NULL, -- FK → identification_requests
member_id BIGINT UNSIGNED NULL, -- FK → members (연동 시)
-- KCP 응답 코드
res_cd VARCHAR(4) NOT NULL, -- 결과 코드
res_msg VARCHAR(255), -- 결과 메시지
-- 인증 정보
cert_no VARCHAR(14), -- 인증 번호
enc_cert_data TEXT, -- 암호화된 개인정보
up_hash VARCHAR(255), -- 요청 해시
dn_hash VARCHAR(255), -- 응답 해시
-- 본인 정보
phone_no VARCHAR(11),
user_name VARCHAR(100),
birth_day VARCHAR(8), -- YYYYMMDD
sex_code VARCHAR(2), -- 01:남, 02:여
local_code VARCHAR(2), -- 01:내국인, 02:외국인
comm_id VARCHAR(3), -- 통신사 코드
-- CI/DI (중복가입 확인)
ci VARCHAR(88), -- Connecting Information
di VARCHAR(64), -- Duplicate Information
created_at TIMESTAMP,
updated_at TIMESTAMP
);
CI/DI란?
- CI (Connecting Information): 주민등록번호를 대체하는 개인 고유 식별값. 모든 서비스에서 동일한 사람은 동일한 CI를 갖는다.
- DI (Duplicate Information): 서비스(가맹점)별 고유값. 같은 사람이라도 서비스마다 다른 DI를 갖는다.
CI — "이 사람은 이전에 다른 서비스에서도 인증한 적 있는가?"
DI — "이 사람은 우리 서비스에서 이미 가입했는가?"
요청-결과 분리 패턴의 장점
- 감사 추적: 인증 요청 자체를 기록하여, 결과와 무관하게 시도 이력 관리
- IP/UA 기록: 부정 사용 탐지 (동일 IP에서 대량 인증 시도 등)
- 1:N 관계: 동일 요청에 재시도 결과를 연결 가능
- 회원 연동 지연: 인증 시점에 회원이 없을 수 있으므로
member_id는 nullable
보안 고려사항
enc_cert_data는 KCP가 암호화한 상태로 수신 — 복호화 키 관리 필요up_hash/dn_hash로 요청-응답 무결성 검증- CI/DI는 개인정보에 해당 — DB 접근 제한, 로그 마스킹 필수
- HTTPS 전용 콜백 URL 사용
- 콜백 URL에서 KCP 서버 IP 화이트리스트 검증 권장
핵심 정리
KCP 본인인증 통합의 핵심은 “서버는 폼 생성과 결과 수신만 한다”는 점이다. 인증 로직은 KCP가 전담하므로, 우리 쪽 구현은 단순하다. 다만 CI/DI 같은 민감 정보의 저장/관리와, 콜백 URL의 보안(IP 화이트리스트, HTTPS)에 주의해야 한다.
참고 자료
- NHN KCP 개발자 센터 — KCP 공식 개발 문서
- PortOne KCP 본인인증 연동 — PortOne KCP 본인인증 가이드