Simple & Easy Notification Service
SMS, PUSH, 카카오 알림톡 등을 사용할 수 있는 서비스입니다. SMS의 경우 건당 9원, PUSH의 경우 건당 0.02원, 알림톡의 경우 건당 7.5원 등 비용이 발생합니다. (자세한 비용은 네이버 클라우드 플랫폼에서 확인 가능합니다.)
* 월 무료 구간 (SMS 50건, PUSH 2000건)이 있기 때문에 토이프로젝트를 하며 사용해봤습니다.
문자 발송 API를 사용하기 위해 먼저 서비스에 가입을 해야합니다.
이후 Simple & Easy Notification Serive에서 프로젝트를 생성합니다.
플랫폼에서 제공하는 API 사용 가이드입니다. 실제 예시 코드와 필수, 옵션 파라미터에 관한 내용까지 모두 있으니 아래 코드에서 부족한 부분은 참고하시면 좋을 것 같습니다.
아래부터는 실제 SMS 발송에 사용되는 코드에 대한 내용입니다.
API 요청 URL 및 HTTP Header에 담아서 보내야 하는 정보입니다.
POST 형식으로 보내게 되고, https://sens.apigw.ntruss.com/sms/v2/services/{serviceId}/messages 에서 serviceId라는 Path Variable이 필요합니다.
해당하는 serviceId는 위 과정에서 프로젝트 생성 후 서비스 ID 확인을 통해 ncp:sms:kr으로 시작되는 값을 확인할 수 있습니다.
다음으로 HTTP Header에 필요한 정보입니다.
// 헤더 설정값 세팅
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("x-ncp-apigw-timestamp", time);
headers.set("x-ncp-iam-access-key", accessKey);
// signature 서명
headers.set("x-ncp-apigw-signature-v2", getSignature(time));
전체 코드에서 다시 보게 되겠지만 HttpHeaders 객체를 생성하여 key, value 형식으로 필요한 header 설정값들을 세팅합니다.
먼저 x-ncp-apigw-timestamp 값은 Unix Time(=Epoch Time)을 사용하며 '1635083262' 이와 같은 형태입니다.
얻는 방법은 아래 전체 코드에서 확인할 수 있고, Unix Time에 대한 자세한 내용도 글 맨 하단에 링크해놓겠습니다.
x-ncp-iam-access-key 값은 클라우드 플랫폼 -> 마이페이지 -> 계정 관리 -> 인증키 관리에서 'Access Key ID' 및 'Secret Key'를 확인할 수 있습니다. Secret Key는 아래 signature를 만드는데 사용됩니다.
private String getSignature(String time) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
String space = " ";
String newLine = "\n";
String method = "POST";
String url = "/sms/v2/services/" + serviceId + "/messages";
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.getEncoder().encodeToString(rawHmac);
return encodeBase64String;
}
Signature를 생성하는 메서드입니다. 시그니처 키를 만드는데 필요한 값은 위에서 사용되었던 두 값인 serviceId, accessKey와 추가로 secretKey가 필요합니다.
해당 값들은 노출될 우려가 있기 때문에 하드코딩으로 코드에 직접 입력하는 것이 아니라 .properties 또는 .yml 파일을 통해 읽어와서 사용하는 것이 좋습니다.
* 여기서 주의할 점은 위에서 사용된 x-ncp-apigw-timestamp의 Unix Time 값과 signature를 만들 때 사용되는 time 값이 같아야 한다는 것입니다.
+ 추가로
String은 불변 객체로 한번 생성하면 값이 변하지 않기 때문에 생성된 String 객체에 + 로 값을 계속 더하게 된다면 더할 때마다 계속해서 새로운 String 객체가 생성됩니다. 그렇기 때문에 다음과 같은 상황에서는 가변의 속성을 가진 StringBuilder를 사용하는 것이 좋습니다.
public MembersResDto.SmsResponse sendSmsForSmsCert(String phoneNumber, String content) throws JsonProcessingException, InvalidKeyException, NoSuchAlgorithmException, URISyntaxException, UnsupportedEncodingException {
String time = Long.toString(System.currentTimeMillis());
// 메세지 생성
List<MembersReqDto.SmsRequest.SmsMessage> smsMessageList = new ArrayList<>();
MembersReqDto.SmsRequest.SmsMessage smsMessage = new MembersReqDto.SmsRequest.SmsMessage(phoneNumber, content);
smsMessageList.add(smsMessage);
MembersReqDto.SmsRequest smsRequest = new MembersReqDto.SmsRequest();
smsRequest.setMessages(smsMessageList);
// json 형태로 변환
ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(smsRequest);
// 헤더 설정값 세팅
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("x-ncp-apigw-timestamp", time);
headers.set("x-ncp-iam-access-key", accessKey);
// signature 서명
headers.set("x-ncp-apigw-signature-v2", getSignature(time));
HttpEntity<String> body = new HttpEntity<>(jsonBody, headers);
MembersResDto.SmsResponse smsResponse = restTemplate.postForObject(new URI("https://sens.apigw.ntruss.com/sms/v2/services/" + serviceId + "/messages"), body, MembersResDto.SmsResponse.class);
return smsResponse;
}
그래서 애초에 문자 메시지를 발송하는 메서드가 실행될 때 System.currentTimeMillis(); 코드를 통해 가져온 유닉스 타임 값을 x-ncp-apigw-timestamp에 value로 주고, signature를 만드는데 인자로도 주었습니다.
이렇게 되면 SMS 발송을 위한 URL, Header 준비는 끝이 났습니다.
{
"type":"(SMS | LMS | MMS)",
"contentType":"(COMM | AD)",
"countryCode":"string",
"from":"string",
"subject":"string",
"content":"string",
"messages":[
{
"to":"string",
"subject":"string",
"content":"string"
}
],
"files":[
{
"name":"string",
"body":"string"
}
],
"reserveTime": "yyyy-MM-dd HH:mm",
"reserveTimeZone": "string",
"scheduleCode": "string"
}
다음으로 SMS 발송 요청 Body입니다.
복잡해 보이지만 필수로 필요한 데이터는 type, from, content, messages, messages.to 가 전부입니다.
messages 내에 subject, content를 정의하지 않으면 기본 subject, content로 발송되며, messages 내에 있는 subject, content가 기본 subject, content보다 우선순위가 높습니다.
@Getter
@Setter
public static class SmsRequest {
private String type = "SMS";
private String contentType = "COMM";
private String countryCode = "82";
private String from = "01012345678";
private String content = "회원가입 휴대폰 인증 코드입니다.";
private List<SmsMessage> messages;
@Getter
@Setter
@AllArgsConstructor
public static class SmsMessage {
private String to;
private String content;
}
}
실제로 SMS를 발송할 때 사용한 객체입니다. 해당 객체를 생성하여 json String 형태로 변환하여 RestTemplate을 사용하여 통신했습니다.
내부적인 로직은 상황에 따라서 다르기 때문에 위에 전체적인 코드는 참고만 해주시고 필요에 따라 응용해서 사용하시면 될 것 같습니다.
* 발송 객체에 사용된 from 값은 생성된 프로젝트에서 미리 등록(인증)한 값만 사용할 수 있습니다.
* RestTemplate에 관한 내용은 함께 정리하기에는 많기 때문에 따로 정리하여 아래 같이 링크 달도록 하겠습니다.
{
"requestId":"string",
"requestTime":"string",
"statusCode":"string",
"statusName":"string"
}
응답 Body
결론적으로 SMS를 발송하는 sendSmsForSmsCert() 메서드가 실행되면 요청에 대한 Response 형식으로 결과를 받을 수 있게 됩니다.
잘못된 내용이나 궁금한 점은 댓글 달아주시면 답변드리겠습니다. 감사합니다.
위 내용 중 함께 보면 좋은 자료
'Programming > Spring Boot' 카테고리의 다른 글
Querydsl 개념 및 Gradle 환경설정 (gradle-7.x.x) (0) | 2021.12.07 |
---|---|
SpringBoot 웹소켓(WebSocket) 채팅 프로그램 파헤치기 (1) | 2021.11.20 |
JPA @CreatedDate @LastModifiedDate 생성 시간, 수정 시간이 저장되는 원리 (4) | 2021.11.06 |
스프링부트 REST API Excel download 방법 (2) | 2021.10.22 |
@NotNull @NotEmpty @NotBlank 차이점 한번은 알고 가자 (2) | 2021.10.14 |