휴대폰 번호로 인증하기

woonie·2022년 9월 6일
0

진행중인 프로젝트에 이메일or휴대폰 으로 인증을 구현할 일이 생겼다.
먼저 이메일 인증을 해봤고 휴대폰 인증까지 해보려고 한다.

많은 구글링을 했고 대부분 coolsms를 많이들 사용했는데 NAVER CLOUD에서도 지원하는 SMS API가 있기에
조금 더 친숙?한 네이버 클라우드를 선택했다.

1. NAVER SMS API 환경설정

  • 1-1. 먼저 네이버 클라우드 플랫폼에 가입 후 콘솔로 이동
    https://www.ncloud.com/
    https://console.ncloud.com/sens/home

  • 2-2. Simple & Easy Notification Service (SMS 서비스의 이름이다.) 의 Home을 찾아가 프로젝트 생성하기를 누른다.

  • 2-3. 원하는 설정값을 입력하고 생성한다. 나는 SMS만 이용하기에 SMS만 선택했다.

  • 2-4. 발신번호 등록

  • SMS > CallingNumber에서 발신번호를 등록해준다.
    나는 연습용이기에 내 개인 휴대폰 번호로 설정했다.

2. 환경 변수 설정

API요청을 위한 KEY와 ID값을 발급받아야 한다.

다른 서비스와 다르게 네이버 클라우드는 토큰 발급 없이 헤더에 명시한 값들을 넣어주면 API기능을 요청할 수 있다.

2-1. KEY

먼저, KEY값은 마이페이지 > 계정 관리 > 인증키 관리 > 신규 API 인증키 생성 하면 된다.

생성된 AccessKey와 SecretKey를 잘 백업해둬야 한다.

2-2. ServiceId

다음은 ServiceId를 가져오자. 해당 값은 SMS서비스 프로젝트를 만들때 이미 발급되었다.
프로젝트 콘솔로 돌어가 서비스ID(열쇠모양)를 클릭하면 서비스ID를 발급받을 수 있다.

이렇게 필요한 환경구성은 끝났다.

3. SMS 전송을 위한 헤더 구성

SMS API 요청을 하기 위해선 NAVER에서 지정해둔 포맷에 따라 헤더를 구현해야 한다.
(참고 : https://api.ncloud-docs.com/docs/common-ncpapi)

먼저 프로젝트의 properties에서 발급받은 KEY값과 serviceID를 설정하자.
직접 코드에 그대로 붙이기에는 조금 찜찜하다.

3-1. application.properties

naver-cloud-sms.accessKey=
naver-cloud-sms.secretKey=
naver-cloud-sms.serviceId=
naver-cloud-sms.senderPhone=01012345678 (발신번호)

3-2. build.gradle

    implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'

3-3. SmsService

  • 암호화가 필요한 헤더 구성을 해야한다.
@PropertySource("classpath:application.properties")
@Slf4j
@RequiredArgsConstructor
@Service
public class SmsService {
    //휴대폰 인증 번호
    private final String smsConfirmNum = createSmsKey();
    private final RedisUtill redisUtil;

    @Value("${naver-cloud-sms.accessKey}")
    private String accessKey;

    @Value("${naver-cloud-sms.secretKey}")
    private String secretKey;

    @Value("${naver-cloud-sms.serviceId}")
    private String serviceId;

    @Value("${naver-cloud-sms.senderPhone}")
    private String phone;

    public String getSignature(String time) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        String space = " ";
        String newLine = "\n";
        String method = "POST";
        String url = "/sms/v2/services/"+ this.serviceId+"/messages";
        String accessKey = this.accessKey;
        String secretKey = this.secretKey;

        String message = new StringBuilder()
                .append(method)
                .append(space)
                .append(url)
                .append(newLine)
                .append(time)
                .append(newLine)
                .append(accessKey)
                .toString();

        SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(signingKey);

        byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
        String encodeBase64String = Base64.encodeBase64String(rawHmac);

        return encodeBase64String;
    }
}

4. 메시지 발송 - Request & Response 생성

메시지 요청을 위한 Request 객체와 API 요청 반환값을 담아올 Response 객체를 생성해둔다.
요청, 반환에 쓰이는 필드값은 공식 문서에 아주 상세히 나와있다. 참고하자.
https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송

4-1. MessageDto

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class MessageDto {
    private String to;
//    String content;
}

나는 별도 문자 메시지 발송이 아닌 문구는 동일하고 인증번호만 달리 보내기에 content는 사용하지 않았다.

4-2. SmsRequestDTO, SmsResponseDTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class SmsRequestDto {
    private String type;
    private String contentType;
    private String countryCode;
    private String from;
    private String content;
    private List<MessageDto> messages;
}
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class SmsResponseDto {
    private String requestId;
    private LocalDateTime requestTime;
    private String statusCode;
    private String statusName;
    private String smsConfirmNum;
}

5. 메시지 발송

  • SmsService
    public SmsResponseDto sendSms(MessageDto messageDto) throws JsonProcessingException, RestClientException, URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
        String time = Long.toString(System.currentTimeMillis());

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("x-ncp-apigw-timestamp", time);
        headers.set("x-ncp-iam-access-key", accessKey);
        headers.set("x-ncp-apigw-signature-v2", getSignature(time)); // signature 서명

        List<MessageDto> messages = new ArrayList<>();
        messages.add(messageDto);

        SmsRequestDto request = SmsRequestDto.builder()
                .type("SMS")
                .contentType("COMM")
                .countryCode("82")
                .from(phone)
                .content("[서비스명 테스트닷] 인증번호 [" + smsConfirmNum + "]를 입력해주세요")
                .messages(messages)
                .build();

        //쌓은 바디를 json형태로 반환
        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(request);
        // jsonBody와 헤더 조립
        HttpEntity<String> httpBody = new HttpEntity<>(body, headers);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        //restTemplate로 post 요청 보내고 오류가 없으면 202코드 반환
        SmsResponseDto smsResponseDto = restTemplate.postForObject(new URI("https://sens.apigw.ntruss.com/sms/v2/services/"+ serviceId +"/messages"), httpBody, SmsResponseDto.class);
        SmsResponseDto responseDto = new SmsResponseDto(smsConfirmNum);
       // redisUtil.setDataExpire(smsConfirmNum, messageDto.getTo(), 60 * 3L); // 유효시간 3분
        return smsResponseDto;
    }



    // 인증코드 만들기
    public static String createSmsKey() {
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();

        for (int i = 0; i < 5; i++) { // 인증코드 5자리
            key.append((rnd.nextInt(10)));
        }
        return key.toString();
    }
  • SmsController
@RequiredArgsConstructor
@RestController
public class SmsController {

    private final SmsService smsService;

    @PostMapping("/sms/send")
    public SmsResponseDto sendSms(@RequestBody MessageDto messageDto) throws UnsupportedEncodingException, URISyntaxException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException {
        SmsResponseDto responseDto = smsService.sendSms(messageDto);
        return responseDto;
    }
}

6. 테스트

모든 구성은 끝냈고 postman으로 테스트해보자!

profile
동료들과 함께하는 개발의 중요성에 관심이 많습니다. 언제나 호기심을 갖고 꾸준히 노력하는 개발자로서 성장하고 있습니다.

4개의 댓글

comment-user-thumbnail
2023년 11월 15일

혹시 네이버 클라우드 단순 휴대폰 번호만 인증해도 비용이 발생하나요?

1개의 답글
comment-user-thumbnail
2024년 1월 31일

도움좀 받을수 있을까요 ?

1개의 답글