Skip to content
isdnetworks
Go back

NHN KCP 본인인증을 Laravel에 통합한 구조

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 — "이 사람은 이전에 다른 서비스에서도 인증한 적 있는가?"
DI — "이 사람은 우리 서비스에서 이미 가입했는가?"

요청-결과 분리 패턴의 장점

  1. 감사 추적: 인증 요청 자체를 기록하여, 결과와 무관하게 시도 이력 관리
  2. IP/UA 기록: 부정 사용 탐지 (동일 IP에서 대량 인증 시도 등)
  3. 1:N 관계: 동일 요청에 재시도 결과를 연결 가능
  4. 회원 연동 지연: 인증 시점에 회원이 없을 수 있으므로 member_id는 nullable

보안 고려사항

핵심 정리

KCP 본인인증 통합의 핵심은 “서버는 폼 생성과 결과 수신만 한다”는 점이다. 인증 로직은 KCP가 전담하므로, 우리 쪽 구현은 단순하다. 다만 CI/DI 같은 민감 정보의 저장/관리와, 콜백 URL의 보안(IP 화이트리스트, HTTPS)에 주의해야 한다.

참고 자료


Share this post on:

Previous Post
LiteLLM Proxy 폴백 구조를 4단에서 2단으로 줄인 이유
Next Post
Linux 서버 4대의 환경을 통일한 과정