naver sens axios 에러

권태형·2023년 5월 18일
1

bnb Back-Server Project

목록 보기
9/31

카카오도 그러더니 네이버도 axios에러를 뱉어낸다. 다른점은 AxiosError: Request failed with status code 401로 에러코드가 401번이라는 점이다. 이전 카카오의 axios에러는 400에러로 badrequest에러 즉 전송정보에 문제가 발생했기 때문에 발생한 에러였는데..

이번엔 401에러로 권한인증 에러를 뱉어낸다. 보통 오픈API의 권한은 해당 플랫폼에서 발급하는 accesskey와 secretkey로 처리하는데 분명 이번에도 적절하게 네이버 클라우드 플랫폼에서 발급하는 API 엑세스키와 시크릿키를 작성에서 넣어줬는데도, 또 에러를 뱉어낸다.

해당 에러에 대해서 플랫폼의 AI봇에게 질문하였지만, 원하는 응답을 해주지 못했다.

실시간 채팅상담은 오후5시 까지 인데 해당에러가 발생한 시점이 5시3분경에 확인되었다. axios에러는 401을 뱉어내는데, 왜 코드는 ERR_BAD_REQUEST일까? 이 부분은 잘 모르겠다.

1. 따라서 구글링을 통해 같은 에러를 격는 사람이 있나 찾아보았다.

C#코드로 로직을 작성한 포스팅을 보았는데 이분은 timestemps를 문자열 처리를 하지않고 헤더에 보내고, 시그니처를 Base64로 인코딩 하지 않고 헤더에 보내서 문제가 발생한 경우였다.

나는 두 가지 모두 처리를 해 두었기에 해당사항에 대해서 문제가 발생하지 않았을 것이다.

2. 다른 포스팅을 막 찾다가 네이버 오픈 API 에러 코드 목록을 보여주는 사이트를 찾았다.

401에러가 발생하는 원인을 4가지로 나열해 주었다.

  1. 애플리케이션 클라이언트 아이디와 시크릿 값이 없거나 잘못되었을 경우
  • 이 사항에 대해서는 필자의 accesskeyID와 secretkey를 확실하게 해당 플랫폼에서 복사해서 넣었고 몇 번을 확인했기 때문에 문제 없었다.
  1. 클라이언트 아이디와 시크릿 값을 HTTP 헤더에 정확히 설정하지 않고 호출했을 경우
  • 이 사항 또한, 헤더설정에 env에 작성해둔 accesskeyID를 정확히 입력했기 때문에 문제 없을 것이다.
  1. API 권한 설정이 안되어 있을 경우
  • 이 사항에 대한 조치방안으로 "내 애플리케이션 메뉴의 ‘API 설정’ 탭에서 그 API가 추가되어 있는지 확인합니다."라고 나왔는데 네이버 클라우드 플랫폼에는 내 애플리케이션 메뉴와 API설정탭은 존재하지 않았다. 이 내용들은 모두 네이버 디벨로퍼스에 있는 등록한 어플리케이션에 대한 내용이고, 여기서 사용할 수 있는 API들 중에 SENS는 존재하지 않았다.
  1. 로그인 오픈 API를 호출할 때 접근 토큰(access_token) 값이 빠졌거나 잘못된 값(만료)을 설정하였을 경우
  • 이 사항은 네이버 로그인 API를 이용할 떄 발생하는 401에러에 대한 처리방법만 제시해 주었다.

결과적으로는 해당 사이트에서도 내가 원하는 정답을 얻을 수 없었다.

3. 시그니처 생성 코드를 샘플과 같은 버전으로 변경해보았다.

네이버sens에서 시그니처 생성에 대한 코드 샘플이 있었는데, 여기서 crypto-js를 이용해 해싱을 하는 부분이 3.1.2버전으로 조금 오래된 과거 버전으로 샘플이 작성되어있다.

따라서 본인은 최신 4.1.1버전을 받아서 똑같이 SHA256해싱하고 Base64로 변환하는 과정을 거쳐 시그니처를 생성했다.

makeSignature(): string {
    const space = ' ';
    const newLine = '\n';
    const method = 'POST';
  	const timestamp = Date.now().toString()
    const url = `/sms/v2/services/${NAVER_SMS_SERVICE_ID}/messages`;
    const accessKey = NAVER_API_ACCESS_KEY_ID;
    const secretKey = NAVER_API_SECRET_KEY;

    const massage =
      method + space + url + newLine + timestamp + newLine + accessKey;
    const hmac = CryptoJS.HmacSHA256(massage, secretKey);
    const hash = hmac.toString(CryptoJS.enc.Base64);
    return hash;
  }

이게 원인인지 알 수 없었지만, 그래도 샘플과 많은 SENS사용된 포스팅에서는 과거의 3.1.2버전의 샘플예시과 동일하게 작성한 부분이 많아서 최신버전을 지우고 샘플버전 예시와 같이 작성해 보았다.

makeSignature() {
    const space = ' ';
    const newLine = '\n';
    const method = 'POST';
    const timestamp = Date.now().toString()
    const url = `/sms/v2/services/${NAVER_SMS_SERVICE_ID}/messages`;
    const accessKey = NAVER_API_ACCESS_KEY_ID;
    const secretKey = NAVER_API_SECRET_KEY;

    const hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, secretKey);
    hmac.update(method);
    hmac.update(space);
    hmac.update(url);
    hmac.update(newLine);
    hmac.update(timestamp);
    hmac.update(newLine);
    hmac.update(accessKey);

    const hash = hmac.finalize();

    return hash.toString(CryptoJS.enc.Base64);
  }

하지만 이 방법 또한 같은 에러를 내뿜었다.

4. 네이버 클라우드 플랫폼 문의하기

5시 부터 8시 까지 해당 에러를 해결하기 위해서 정보의 바다를 휘저었지만, 도저히 찾을 방법이 보이지 않아서 일단은 네이버 클라우드 플랫폼의 문의하기를 이용해 문의내용을 남겨두었다.

퇴근시간이 지났음에도 불구하고 답변이 왔다. 답변내용을 보자마자. 😲헉! "시그니처의 timestemp와 헤더의timestemp 같아야 하는구나.. 나는 머리가 1차원적이구나"라고 생각했다.

//시그니처 제작
makeSignature(): string {
     const space = ' ';
     const newLine = '\n';
     const method = 'POST';
     const url = `/sms/v2/services/${NAVER_SMS_SERVICE_ID}/messages`;
     const timestamp = Date.now().toString();
     const accessKey = NAVER_API_ACCESS_KEY_ID;
     const secretKey = NAVER_API_SECRET_KEY;

     const massage =
       method + space + url + newLine + timestamp + newLine + accessKey;
     const hmac = CryptoJS.HmacSHA256(massage, secretKey);
     const hash = hmac.toString(CryptoJS.enc.Base64);
     console.log(hash);
     return hash;
   }
//sms보내기
async sendSMS(sendSmsDto: SendSmsDto) {
    const signature: string = this.makeSignature();
    const code: string = this.createRandomNum().toString();
    const headers = {
      'Content-Type': 'application/json; charset=utf-8',
      'x-ncp-apigw-timestamp': Date.now().toString();,
      'x-ncp-iam-access-key': NAVER_API_ACCESS_KEY_ID,
      'x-ncp-apigw-signature-v2': signature,
    };

    const body = {
      type: 'SMS',
      from: SMS_CALLING_NUMBER,
      content: `인증번호는 ${code}입니다.`,
      messages: [{ to: sendSmsDto.phone_number }],
    };

    const url = `https://sens.apigw.ntruss.com/sms/v2/services/${NAVER_SMS_SERVICE_ID}/messages`;
    const res = await firstValueFrom(
      this.http.post(url, body, { headers }),
    ).catch((e) => {
      console.error(e);
      throw new InternalServerErrorException();
    });
    console.log(res.data);
  }

내 로직은 위와 같이 각각의 함수를 나눠서 생각했기 때문에 각각 Date.now().toString()로 요청을 감지한 시간을 스트링으로 변환한다. 하지만 sms보내기 로직에서 시그니처를 먼저 만들고 이후에 헤더를 만들게 되어, 사람은 느낄 수 없지만 컴퓨터가 인지하는 밀리초 단위의 차이가 발생하기 때문에 헤더에 작성된 'x-ncp-apigw-timestamp'헤더가 시그니처의 암호화된 시간과 일치하지 않아서 발생한 문제였다.

profile
22년 12월 개발을 시작한 신입 개발자 ‘권태형’입니다. 포스팅 하나하나 내가 다시보기 위해 쓰는 것이지만, 다른 분들에게도 도움이 되었으면 좋겠습니다. 💯컬러폰트가 잘 안보이실 경우 🌙다크모드를 이용해주세요.😀 지적과 참견은 언제나 환영합니다. 많은 댓글 부탁드립니다.

0개의 댓글