ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Next.js] Nice API 본인인증 구현하기
    Dev 2022. 8. 17. 18:00


    1. NICE 표준창 호출 

    NICE 인증과정

    NICE 본인인증 과정은 위 그림과 같다.

    위 그림의 "기관 토큰 발급 암호화 토큰 발급 대칭키 생성 요청정보 암호화" 과정은 백엔드 개발자가 api로 만들어 표준창 호출에 필요한 정보들만을 받을 수 있도록 구현해주었다.

    이 api를 token api라고 가정한다.

     

    token api의 응답 값은 NICE 표준창 호출에 필요한 token_version_id, enc_data, integrity_value 이 세 가지 정보이며,

    이 세가지 정보가 유효해야만 NICE 표준 창을 호출할 수 있다. 표준창 호출을 위해서는 팝업 또는 새 창을 열고 해당 창으로 form submit 하여 암호화된 데이터를 전달해주어야 한다.

    암호화 데이터 전달을 위한 코드는 아래의 button 코드를 제외한 form 코드이며, 이는 NICE API 개발 가이드 페이지에서 다운로드 할 수 있는 api 가이드 문서에서 확인할 수 있는 코드이다.

    <!-- 표준창 호출시 필요한 데이터 전송을 위한 form -->
        <form name="form" id="form" action="https://nice.checkplus.co.kr/CheckPlusSafeModel/service.cb">
            <input type="hidden" id="m" name="m" value="service" />
            <input type="hidden" id="token_version_id" name="token_version_id" value="" />
            <input type="hidden" id="enc_data" name="enc_data" />
            <input type="hidden" id="integrity_value" name="integrity_value" />
        </form>
    
        <button onClick={onClickCertify}> 휴대폰 인증 </button>

     

    위와 같이 form을 작성해주고, 휴대폰 인증 버튼 클릭 시 실행될 함수를 아래와 같이 작성해주면 NICE 표준 창이 호출된다.

    // 휴대폰 인증 버튼 클릭시 실행되는 함수, NICE 표준창 호출
     const onClickCertify = async () => {
            const { form } = document;
            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}`;
            const returnUrl = `${url}/api/nice`;  // 본인인증 결과를 전달받을 api url
    
            // 위에서 언급했던 token api가 요청 데이터를 암호화한 후 표준창 호출에 필요한 데이터를 응답해준다.
            const res = await api.get<NiceEncodeType>('/api/token', { returnUrl });
    
            if (form && res.data) {
                const { enc_data, integrity_value, token_version_id } = res.data;
                window.open('', 'nicePopup', option);
    
                form.target = 'nicePopup';
                form.enc_data.value = enc_data;
                form.token_version_id.value = token_version_id;
                form.integrity_value.value = integrity_value;
                form.submit();
            }
        };

    이제 위의 함수가 실행되면 표준창이 호출되고, 휴대폰 인증을 할 수 있다.

     

     

     

    2. 인증 데이터의 처리 

    인증이 완료되면 NICE가 returnUrl로 요청하여 유저 데이터 복호화에 필요한 데이터들을 보내준다.

    남은 일은 해당 데이터들을 이용해 복호화한 후 나온 유저 데이터로 추후 과정을 진행하면 되었다.

     

    그런데 문제가 발생했다. React 기능만을 사용해서는 인증이 완료되는 시점을 명확하게 파악하여 추후 필요한 처리를 하지 못한다는 것이었다. 보통 구글링으로 찾아볼 수 있는 예제들은 php가 많았는데, php를 잘 알지는 못해서 확실하진 않지만 jquery를 사용해서 dom 조작을 통해 인증 결과 데이터를 가져오고 있었다. 이러한 과정을 흉내내기 위해 아래와 같은 과정으로 구현하였다.

     

    1. 클라이언트에서 완료 응답 받기

    Next.js api 기능을 사용하여 인증 완료 시 응답을 받을 api를 생성하고, returnUrl를 해당 api로 지정하여 nice 인증 응답을 받는다.

    이렇게 작성하므로서 인증이 완료되는 시점을 파악하는 것에 대한 생각을 할 필요가 없어진다.

     

     

    2. 응답 데이터의 처리

    인증 완료 후 NICE측에서 returnUrl로 요청을 보내면 enc_data, token_version_id, integrity_value 등의 데이터가 *쿼리로 넘어온다.

    (* 웹에서는 쿼리로 넘어오지만, 모바일 디바이스에서는 body로 넘어오기 때문에 해당 부분 분기처리가 필요하다. )

    처음 시도한 방법은 리다이렉트 할 url에 직접 query data를 넣어주어서 리다이렉트 된 페이지에서 꺼내서 사용하는 방향으로 구현했다.

    하지만 이렇게 하니 리다이렉트 후 enc_data가 url encode 되어 오염되어 리다이렉트 된 페이지에서 사용할 수가 없었다.

     

    그래서 next api에서 decode 하는 api를 호출하여 응답 데이터를 확인하고, 서버에서는 해당 응답의 request_no에 해당하는 인증 데이터들(이름, 휴대폰 번호)를 저장하여 클라이언트에서 request_no를 이용하여 추후 필요한 작업을 할 수 있도록 방향을 바꾸어 구현하였다.

    // pages/api 폴더 안에 작성하는 next api 
    export default async function getNiceCertify(req: NextApiRequest, res: NextApiResponse<NiceResType>) {
        const reqData = Object.keys(req.query).length ? req.query : req.body;
    
        const { enc_data: encData, token_version_id: tokenVersionId } = reqData as {
            enc_data: string;
            token_version_id: string;
        };
    
        const parmas = {
            encData,
            tokenVersionId,
        };
        const response = await api.get<certifyUserType>('/nice/decode', parmas);
    
        if (response.ok && response.data) {
            res.redirect(302, `/certify/?reqNo=${response.data.requestno}`);
            return;
        }
        res.redirect(302, `/certify/?error=${response.status}`);
    }

    ❓ 요청에 대한 리다이렉트 응답이 어떻게 팝업 리다이렉트로 이어지는지

    생각해보니 NICE측 요청에 대해 redirect를 하는데 어떻게 현재 NICE 모듈 팝업이 리다이렉트 되는지 의문이었다.

    관련된 문서를 찾지는 못해서 지금까지 요청을 보내는 주체를 모듈 팝업이 아닌 NICE 내부 어떠한 로직이라고 막연하게 생각하고 있었다.

    하지만 다시 생각해보니 만약 내부 어떤 로직이 아니라 요청하는 주체를 NICE 모듈 자체라고 생각하고 보면 과정이 맞아 떨어졌다.

    인증완료 후 모듈 팝업이 returnUrl로 요청을 했고 ➜ 그 요청에 대한 응답을 returnUrl api는 redirect로 전달했으며 ➜ 모듈 팝업은 그 응답을 받아 그대로 실행한 것 

     

     

    3. 인증완료 후 팝업 처리

    위에서 리다이렉트 시키는 페이지는 다른 페이지와 다를 것 없는 next.js 페이지이며, 인증 완료 후 추후 처리를 위해 만든 페이지이다.

    api단에서 모두 처리할 수 있다면 좋겠지만, dom 접근, 조작 등 서버사이드 측에서 할 수 없는 작업들이 있기 때문에 이러한 작업을 위한 페이지를 만들고 해당 페이지로 리다이렉트 시켰다.

     

    정상적인 루트로 해당 페이지로 진입했다면, window.opener는 팝업을 연 부모, window는 팝업창 자체일 것이다.

    이러한 window와 opener를 조작하여 페이지 이동 및 팝업 제거 등의 작업을 실행한다.

    // pages/certify
    import { useCallback, useEffect } from 'react';
    import { useRouter } from 'next/router';
    
    export default function Certify() {
        const route = useRouter();
    
        const handleLoad = useCallback(() => {
            // ...기타 필요 작업
            
            window.opener.document.location = `${page_url}?request_no=${request_no}`
            window.close();
        }, [route.query]);
    
        useEffect(() => {
            if (!window.opener) return route.back(); // 보통 페이지와 다를 것 없기 때문에 비정상적인 방법으로 진입 시 뒤로가기
    
            handleLoad(); // 정상적인 루트로 진입한 경우 실행된다. 
        }, [handleLoad, route.query]);
    
        return <></>;
    }

     

    위와 같은 과정으로 구현하게 되면 유저는 보통의 본인 인증 과정과 같은 경험을 느낄 수 있다.

    본인인증 버튼 클릭 인증 완료  팝업 닫힘 페이지 이동

     

    이번 프로젝트에서는 Next.js를 사용하여 custom api 기능을 사용할 수 있었는데, 정리하면서 생각해보니 React인 경우에도 returnUrl을 백엔드 서버로 향하게 하고, 서버에서 위의 getNiceCertify 코드에서와 동일하게 리다이렉트를 시켜주면 동일한 플로우로 처리할 수 있겠다는 생각이 들었다. 다음에 React 프로젝트에서 NICE 본인인증을 구현할 일이 생기면 해당 방법을 사용하여 구현해봐야겠다.

     

     

     

    댓글

Designed by Tistory.