진행중인 프로젝트에 이메일or휴대폰 으로 인증을 구현할 일이 생겼다.
먼저 이메일 인증을 해봤고 휴대폰 인증까지 해보려고 한다.
많은 구글링을 했고 대부분 coolsms를 많이들 사용했는데 NAVER CLOUD에서도 지원하는 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에서 발신번호를 등록해준다.
나는 연습용이기에 내 개인 휴대폰 번호로 설정했다.
API요청을 위한 KEY와 ID값을 발급받아야 한다.
다른 서비스와 다르게 네이버 클라우드는 토큰 발급 없이 헤더에 명시한 값들을 넣어주면 API기능을 요청할 수 있다.
먼저, KEY값은 마이페이지 > 계정 관리 > 인증키 관리 > 신규 API 인증키 생성 하면 된다.
생성된 AccessKey와 SecretKey를 잘 백업해둬야 한다.
다음은 ServiceId를 가져오자. 해당 값은 SMS서비스 프로젝트를 만들때 이미 발급되었다.
프로젝트 콘솔로 돌어가 서비스ID(열쇠모양)를 클릭하면 서비스ID를 발급받을 수 있다.
이렇게 필요한 환경구성은 끝났다.
SMS API 요청을 하기 위해선 NAVER에서 지정해둔 포맷에 따라 헤더를 구현해야 한다.
(참고 : https://api.ncloud-docs.com/docs/common-ncpapi)
먼저 프로젝트의 properties에서 발급받은 KEY값과 serviceID를 설정하자.
직접 코드에 그대로 붙이기에는 조금 찜찜하다.
naver-cloud-sms.accessKey=
naver-cloud-sms.secretKey=
naver-cloud-sms.serviceId=
naver-cloud-sms.senderPhone=01012345678 (발신번호)
implementation group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.13'
@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;
}
}
메시지 요청을 위한 Request 객체와 API 요청 반환값을 담아올 Response 객체를 생성해둔다.
요청, 반환에 쓰이는 필드값은 공식 문서에 아주 상세히 나와있다. 참고하자.
https://api.ncloud-docs.com/docs/ai-application-service-sens-smsv2#메시지발송
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class MessageDto {
private String to;
// String content;
}
나는 별도 문자 메시지 발송이 아닌 문구는 동일하고 인증번호만 달리 보내기에 content는 사용하지 않았다.
@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;
}
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();
}
@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;
}
}
모든 구성은 끝냈고 postman으로 테스트해보자!
혹시 네이버 클라우드 단순 휴대폰 번호만 인증해도 비용이 발생하나요?