본문 바로가기

내직업은 IT종사자/기타

SPA 클라이언트 NICE API 본인인증 구현하는 방법

반응형

 

 

 

샘플페이지와 적용사례 정보가 부족해서 적용하면서 삽질한 부분들을 기록해보고자 합니다!  

적용해보면 진짜 로직 간단한데 적용하기 까지 과정이 쫌 다사다난했던... ㅠㅠ

NICE API 본인인증을  현재 프로젝트에 적용하는 방법 (2가지)

 

1. NICE API  를 이용해서 적용하는방법

  • NICE API 홈페이지에 NICE와 계약한 계정을 로그인하고 들어가면 마이페이지(?) 에서 API 호출을 위한 [시크릿 키값] 이 있습니다.
  • 이 [시크릿 값]으로 최초 1회  계약한 회사에 1:1로 기관토큰을 발급받아서 이용할 수 있습니다.  (반영구적 사용가능)
  • 발급받은 기관인증 토큰으로 나이스 본인확인 호출하기 위한 암호화토큰을 발급받습니다. (본인확인을 호출할때마다 발급, 유효기간이 있습니다.)

 

2. NICE 모듈을 이용해서 적용하는 방법 (이 방법 적용)

  • 먼저 백엔드 환경에 따라 맞는 모듈이 필요합니다. 모듈 종류로는 JSP, ASP, .net, js가 있습니다.
  • 혹시 제공되는 모듈중에 맞는게 없다면 NICE관계자한테 혹시 있는지 한번 문의 하면 됩니다.
  • 모듈을 사용하기 위해서는 아래가 꼭 필요합니다.
    • 사이트코드,
    • 모듈이 설치된 path 
    • returnUrl(나이스모듈이 본인인증이 끝나고 성공했을때 req를 받아줄 수 있는 api endpoint)
    • errorUrl(나이스 모듈이 본인인증이 끝나고 실패했을때 req를 받아줄 수 있는 api endpoint)

 

nice에서 모듈버전을 보내줄때 샘플페이지도  받을 수 있습니다.

 

 

프론트엔드(클라이언트)는 아래와 같이 구현하면 됩니다.

 

1. form.vue

<form
    name="passForm"
    method="post”
  >
    <!-- 본인인증 PASS 표준 인증창 호출을 위한  hidden value -->
    <input
      type="hidden"
      name="m"
      value="checkplusService"
    >
    <!-- 필수 데이타로, 누락하시면 안됩니다. -->
    <input
      type="hidden"
      name="EncodeData"
    >
    <!-- // 본인인증 PASS 표준 인증창 호출을 위한  hidden value -->

	<button onClick=“callPassForm”> nice 표준인증창 호출 버튼 </button>
  </form>

 

우선 hidden input 2개는 꼭 있어야되고  버튼을 눌렀을때 나이스 모듈을 이용해서 encData를 받아와서 두번째 hidden태그에

넣어준 상태로 submit해야  우리가 흔히 보는 본인인증 표준창이 띄워지는것을 볼 수 있습니다.

 

아래는 script 코드입니다.

 

<script>
import axios from ‘axios’;

// axios 인스턴스생성은 여기말고 어딘가에 해놓은 상태

axios.get('http://localhost:8888/getEncData')
.then(res => {
	const { passForm } = document;

      if (passForm && res.endData) {
      	// 택1
      	// 1. 팝업창으로 띄우는 방법
        const left = screen.width / 2 - 500 / 2;
      	const top = screen.height / 2 - 800 / 2;
      	const option = `status=no, menubar=no, toolbar=no, resizable=no, width=500, height=600, left=${left}, top=${top}`;
        window.open('', 'nicePopup', option);
        
        passForm.action = 'https://nice.checkplus.co.kr/CheckPlusSafeModel/checkplus.cb';
        passForm.EncodeData.value = res.endData;
        passForm.target = 'popupChk';
        passForm.submit();
        
        // 2. 새로운페이지(새탭)으로 띄우는 방법
        passForm.action = 'https://nice.checkplus.co.kr/CheckPlusSafeModel/checkplus.cb';
        passForm.EncodeData.value = res.endData;
        passForm.target = 'popupChk';
        passForm.submit();
      }
}
</script>

 

 

백엔드에서는 

1. 나이스 모듈을 이용해서 enc data를  받아서 response로 보내줄 api

2. 나이스모듈이 결과를 뱉었을때 받아줄  return api, error api가 필요합니다. 

 

일단 저는 나이스 모듈 js버전을 받아서 사용했고, 프론트와 백엔드 나눠서 작업하였기 때문에 백엔드 역할을 해주기위해 node js로  서버를 하나 띄워서 구현했습니다. 일단 테스트용으로 전부 로컬에서 진행했습니다.

 

1. niceConfig.js

//modluePath: 모듈의 절대 경로(권한:755 , FTP업로드방식 : binary)
// ex) sModulePath = "C:\\module\\CPClient.exe";
//     sModulePath = "/root/modile/CPClient";

//sitecode, sitepassword: 나이스 담당자로부터 전달 받은 계약 상품에 따른 정보

module.exports = {
  modulePath: "/Users/~~~~~상세경로/CPClient_mac",
  sitecode: "나이스로 부터받은 사이트코드",
  sitepw: "나이스로부터 받은 사이트비밀번호"
}

 

2. App.js

const express = require('express');
const app = express();
const cors = require('cors');
const qs = require("querystring");
const exec = require("child_process").exec; // child_process 모듈 추가
const port = 8888;
const niceConfig = require("./niceConfig");

// CORS 미들웨어 활성화
app.use(cors());

// NICE표준인증창을 띄우기 위한 Enc_data를 생성해서 FE에 전달주는 API
app.get('/getEncData', (req, res, next) => {
  const sAuthType = "M";      	  //없으면 기본 선택화면, M(휴대폰), X(인증서공통), U(공동인증서), F(금융인증서), S(PASS인증서), C(신용카드)
  const sCustomize 	= "Mobile";			  //없으면 기본 웹페이지 / Mobile : 모바일페이지
  const {sitecode, sitepw, modulePath } = niceConfig;

 // 본인인증 처리 후, 결과 데이타를 리턴 받기위해 다음예제와 같이 http부터 입력합니다.
  const sReturnUrl = "http://localhost:8888/check_success";	// 성공시 이동될 URL (방식 : 프로토콜을 포함한 절대 주소)
  const sErrorUrl = "http://localhost:8888/check_fail";	  	// 실패시 이동될 URL (방식 : 프로토콜을 포함한 절대 주소)

  const d = new Date();
  const sCPRequest = sitecode + "_" + d.getTime();

  //전달 원문 데이터 초기화
  let sPlaincData = "";
  //전달 암호화 데이터 초기화
  let sEncData = "";

  sPlaincData = "7:REQ_SEQ" + sCPRequest.length + ":" + sCPRequest +
                "8:SITECODE" + sitecode.length + ":" + sitecode +
                "9:AUTH_TYPE" + sAuthType.length + ":" + sAuthType +
                "7:RTN_URL" + sReturnUrl.length + ":" + sReturnUrl +
                "7:ERR_URL" + sErrorUrl.length + ":" + sErrorUrl +
                "9:CUSTOMIZE" + sCustomize.length + ":" + sCustomize;
  console.log("["+sPlaincData+"]");
    
  const cmd = modulePath + " " + "ENC" + " " + sitecode + " " + sitepw + " " + sPlaincData;

  const child = exec(cmd , {encoding: "euc-kr"});
  child.stdout.on("data", (data) => {
    sEncData += data;
  });

  //exec 실행이 종료 했을때 대한 이벤트 헨들러 
  child.on("close", () => {
  //이곳에서 result처리
  //처리 결과 메시지
  let sRtnMSG = "";
    //처리 결과 확인
    if (sEncData == "-1"){
      sRtnMSG = "암/복호화 시스템 오류입니다.";
    }
    else if (sEncData == "-2"){
      sRtnMSG = "암호화 처리 오류입니다.";
    }
    else if (sEncData == "-3"){
      sRtnMSG = "암호화 데이터 오류 입니다.";
    }
    else if (sEncData == "-9"){
      sRtnMSG = "입력값 오류 : 암호화 처리시, 필요한 파라미터 값을 확인해 주시기 바랍니다.";
    }
    else{
      sRtnMSG = "";
    }
    
    res.send({sEncData});
  })
});

 

이부분은 위에  form.vue에서 호출하면 나이스 모듈을 이용해서 encData를  response로 보내주고 그 값을 form에 붙여서  나이스 표준 인증창을 띄울 수 있게 해줍니다. 이때  모듈을 실행시킬때 returnUrl(성공시) 과 errorURl(실패시) 결과를 받을 수 있는 url (프로토콜까지 포함된 절대경로)를 같이 모듈에 보내줍니다.

 

그러면  본인인증창이 뜨고 사용자가 본인인증이 완료되면  결과에 따라서  위에 모듈을 실행시킬때 딸려 들어간 return Url, errorUrl 으로 결과값이 내려옵니다. 

 

 

2-1 App.js(returnUrl)

 

(추가수정)  암호화된 인증결과 데이터가 PC인경우 method GET,  Mobile인경우  method POST 로 들어옵니다.

app.all('/check_success', (req, res, next) => {
  const sEncData = req.method === 'GET' ? req.query.EncodeData : req.body.EncodeData,
  let cmd = "";
  let sRtnMSG = "";
  const {sitecode, sitepw, modulePath } = niceConfig;

  if( /^0-9a-zA-Z+\/=/.test(sEncData) == true){
    sRtnMSG = "입력값 오류";
    requestnumber = "";
    authtype = "";
    errcode = "";
    response.render("checkplus_fail.ejs", {sRtnMSG , requestnumber , authtype , errcode});
  }

  if(sEncData != "")
  {
     cmd = modulePath + " " + "DEC" + " " + sitecode + " " + sitepw + " " + sEncData;
  }

  let sDecData = "";
   //처리 결과 메시지
   

  const child = exec(cmd , {encoding: "euc-kr"});
  child.stdout.on("data", function(data) {
    sDecData += data;
  });
  child.on("close", function() {
    // console.log(sDecData);
  
    //처리 결과 확인
    if (sDecData == "-1"){
      sRtnMSG = "암/복호화 시스템 오류";
    }
    else if (sDecData == "-4"){
      sRtnMSG = "복호화 처리 오류";
    }
    else if (sDecData == "-5"){
      sRtnMSG = "HASH값 불일치 - 복호화 데이터는 리턴됨";
    }
    else if (sDecData == "-6"){
      sRtnMSG = "복호화 데이터 오류";
    }
    else if (sDecData == "-9"){
      sRtnMSG = "입력값 오류";
    }
    else if (sDecData == "-12"){
      sRtnMSG = "사이트 비밀번호 오류";
    }
    else
    {
      //항목의 설명은 개발 가이드를 참조
      res.requestnumber = decodeURIComponent(GetValue(sDecData , "REQ_SEQ"));     //CP요청 번호 , main에서 생성한 값을 되돌려준다. 세션등에서 비교 가능
      res.responsenumber = decodeURIComponent(GetValue(sDecData , "RES_SEQ"));    //고유 번호 , 나이스에서 생성한 값을 되돌려준다.
      res.authtype = decodeURIComponent(GetValue(sDecData , "AUTH_TYPE"));        //인증수단
      res.name = decodeURIComponent(GetValue(sDecData , "UTF8_NAME"));            //이름
      res.birthdate = decodeURIComponent(GetValue(sDecData , "BIRTHDATE"));       //생년월일(YYYYMMDD)
      res.gender = decodeURIComponent(GetValue(sDecData , "GENDER"));             //성별
      res.nationalinfo = decodeURIComponent(GetValue(sDecData , "NATIONALINFO")); //내.외국인정보
      res.dupinfo = decodeURIComponent(GetValue(sDecData , "DI"));                //중복가입값(64byte)
      res.conninfo = decodeURIComponent(GetValue(sDecData , "CI"));               //연계정보 확인값(88byte)
      res.mobileno = decodeURIComponent(GetValue(sDecData , "MOBILE_NO"));        //휴대폰번호(계약된 경우)
      res.mobileco = decodeURIComponent(GetValue(sDecData , "MOBILE_CO"));        //통신사(계약된 경우)
    }
 
    res.redirect(`http://localhost:5173/result?${qs.stringify(res)}`);
  });

})

성공시 모듈로부터 온 정보를 복호화 해서 보내줍니다. 

백엔드 서버와 프론트가 달라서  Front 페이지주소로 redirect 시켜주는데  query string 으로 결과값을 붙여서 보내줍니다. 

(근데 url에 노출되어 내려가면 front 에서 저 받은 값으로 추가적인 로직이 필요할때 사용자가 쉽게 조작할 수 있어 부적합하다고 생각합니다... redirect에 어떤 특정 id만 보내주고  성공 front 페이지가 랜더링 될때 특정 id를 받아서 상세 정보를 조회해서 내려주는 api가 또잇어야되나.. 짧은 지식으로 거까진 잘...)

 

 

 

 

2-2. App.js(ErrorUrl)

app.get("/check_fail", function(request, response) {
  const {sitecode, sitepw, modulePath } = niceConfig;
  const sEncData = request.param('EncodeData')
  let cmd = "";
  
  if( /^0-9a-zA-Z+\/=/.test(sEncData) == true){
    sRtnMSG = "입력값 오류";
    requestnumber = "";
    authtype = "";
    errcode = "";
    res.redirect('http://localhost:5173/result?type=fail&errorcode=${errorcode}');
  }
  
  if(sEncData != "")
  {
     cmd = modulePath + " " + "DEC" + " " + sitecode + " " + sitepw + " " + sEncData;
  }

  let sDecData = "";

  const child = exec(cmd , {encoding: "euc-kr"});
  child.stdout.on("data", function(data) {
    sDecData += data;
  });
  child.on("close", function() {
    
    let sRtnMSG = "";
    //처리 결과 확인
    if (sDecData == "-1"){
      sRtnMSG = "암/복호화 시스템 오류";
    }
    else if (sDecData == "-4"){
      sRtnMSG = "복호화 처리 오류";
    }
    else if (sDecData == "-5"){
      sRtnMSG = "HASH값 불일치 - 복호화 데이터는 리턴됨";
    }
    else if (sDecData == "-6"){
      sRtnMSG = "복호화 데이터 오류";
    }
    else if (sDecData == "-9"){
      sRtnMSG = "입력값 오류";
    }
    else if (sDecData == "-12"){
      sRtnMSG = "사이트 비밀번호 오류";
    }
    else
    {
      //항목의 설명은 개발 가이드를 참조
      const requestnumber = decodeURIComponent(GetValue(sDecData , "REQ_SEQ"));     //CP요청 번호 , main에서 생성한 값을 되돌려준다. 세션등에서 비교 가능
      const authtype = decodeURIComponent(GetValue(sDecData , "AUTH_TYPE"));        //인증수단
      const errcode = decodeURIComponent(GetValue(sDecData , "ERR_CODE"));          //본인인증 실패 코드
    }

    res.redirect(302,'http://localhost:5173/result?type=fail');
  });
});

function GetValue(plaindata , key){
  const arrData = plaindata.split(":");
  let value = "";
  for(i in arrData){
    const item = arrData[i];
    if(item.indexOf(key) == 0)
    {
      const valLen = parseInt(item.replace(key, ""));
      arrData[i++];
      value = arrData[i].substr(0 ,valLen);
      break;
    }
  }
  return value;
}





// 서버 시작
app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

 

 fail은 통신사 오류로 임의 테스트로 발현시키기 어렵다고 합니다. (nice 관계자 분께 여쭤봄..)

 

 

JSP처럼 하나의 서버에 동적으로 페이지를 개발할 수 있고 서버측 렌더링이 가능하면 좀 더 쉽게, 번거롭지 않게 구현이 가능했을것 같습니다..

 

 

참고글:  https://www.happykoo.net/@happykoo/posts/136

 

해피쿠 블로그 - [Node.js] nice 본인인증 모듈 크로스 도메인 환경에서 연동하기(서버)

누구나 손쉽게 운영하는 블로그!

www.happykoo.net

 

 

 

잘못된 정보에 대한 피드백은 언제나 환영입니다  (´▽`ʃƪ)♡

도움이 되셨으면 좋아요도 좋아요(´▽`ʃƪ)♡

 

반응형