본문 바로가기

Spring

spring 휴대폰 번호 인증하기(Naver SMS)

728x90

진행중인 프로젝트에 이메일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
```java
@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으로 테스트해보자!