Springboot + NAVER S.E.N.S 보내기 (V2 헤더 세팅)
사내에서 문자 서비스를 구축해야해서 네이버 nens를 다루게 되었는데 제가 미숙한 탓에 헤더 세팅하는데 어려움이 있었습니다.
(혼자 핑계로 네이버 API 헤더 세팅 어렵다고.. 꿍시렁)
부디 이 글 보시고 헤더 세팅하시는데 어려움을 덜어내셨으면 좋겠습니다.
저는 막상 저 예제만 보고는 잘 모르겠더라고요...
결론은 헤더의 내용을 암호화하여 다시 헤더에 넣어라! 이런 얘기더랍니다...
소스코드 부터 확인해보겠습니다.
public String makeSignature(Long time) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
String space = " "; // one space
String newLine = "\n"; // new line
String method = "POST"; // method
String url = "/sms/v2/services/"+applicationNaverSENS.getServiceid()+"/messages"; // url (include query string)
String timestamp = time.toString(); // current timestamp (epoch)
String accessKey = applicationNaverSENS.getAccesskey(); // access key id (from portal or Sub Account)
String secretKey = applicationNaverSENS.getSecretkey();
String message = new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timestamp)
.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;
}
저는 SMS를 위주로 코딩을 하였습니다.
네이버 플랫폼에서 제시하는 예제랑 조금 다르다면 저는 time값을 인자로 받아서 헤더를 만듭니다.
이 이유는 글 후반부에 말씀드리겠습니다.
보시다시피 accessKey 와 secretKey는 yml파일에서 따로 읽어오셔도 됩니다. (각자의 방식으로 불러오시면 됩니다.)
일단 네이버 SMS 요청 API URL은 https://sens.apigw.ntruss.com 입니다.
URI 는 /sms/v2/services/{자신의 serviceID}/messages 입니다.
서비스 아이디는
자신의 네이버 플랫폼 콘솔로 이동하신 후 위 사진처럼 프로젝트에서 맨 오른쪽에 '서비스ID'에서 확인하실 수 있습니다.
일반메시지를 보내는 예시로 코딩을 해보도록 하겠습니다.
우선은 일반 메시지에서 'messages' json 부분을 구성해줄 클래스를 생성합니다.
해당 게시글에서는 json을 만들기 위해 ObjectMapper 클래스를 사용하였습니다!
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Service;
@Data
@Service
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class MessagesRequestDto {
private String to;
private String content;
}
이후에는 전체적인 body를 만들어줄 json 클래스를 만듭니다.
@Data
@Service
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class SmsRequestDto {
private String type;
private String contentType;
private String countryCode;
private String from;
private String content;
private List<MessagesRequestDto> messages;
}
이후에 헤더와 바디를 조합해 restTemplate로 post 요청을 보냅니다.
이후에는 데이터를 받아줄 Dto도 필요합니다.
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class SendSmsResponseDto {
private String statusCode;
private String statusName;
private String requestId;
private Timestamp requestTime;
}
밑에는 전체 코드 입니다.
@Getter
@Setter
@AllArgsConstructor
@Component
public class SmsService {
private final ApplicationNaverSENS applicationNaverSENS;
public SendSmsResponseDto sendSms(String recipientPhoneNumber, String content) throws ParseException, JsonProcessingException, UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, URISyntaxException {
Long time = new DateCreator().getTimestamp().getTime();
List<MessagesRequestDto> messages = new ArrayList<>();
// 보내는 사람에게 내용을 보냄.
messages.add(new MessagesRequestDto(recipientPhoneNumber,content)); // content부분이 내용임
// 전체 json에 대해 메시지를 만든다.
SmsRequestDto smsRequestDto = new SmsRequestDto("SMS", "COMM", "82", applicationNaverSENS.getSendfrom(), "MangoLtd", messages);
// 쌓아온 바디를 json 형태로 변환시켜준다.
ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(smsRequestDto);
// 헤더에서 여러 설정값들을 잡아준다.
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set("x-ncp-apigw-timestamp", time.toString());
headers.set("x-ncp-iam-access-key", applicationNaverSENS.getAccesskey());
// 제일 중요한 signature 서명하기.
String sig = makeSignature(time);
System.out.println("sig -> " + sig);
headers.set("x-ncp-apigw-signature-v2", sig);
// 위에서 조립한 jsonBody와 헤더를 조립한다.
HttpEntity<String> body = new HttpEntity<>(jsonBody, headers);
System.out.println(body.getBody());
// restTemplate로 post 요청을 보낸다. 별 일 없으면 202 코드 반환된다.
RestTemplate restTemplate = new RestTemplate();
SendSmsResponseDto sendSmsResponseDto = restTemplate.postForObject(new URI("https://sens.apigw.ntruss.com/sms/v2/services/"+applicationNaverSENS.getServiceid()+"/messages"), body, SendSmsResponseDto.class);
System.out.println(sendSmsResponseDto.getStatusCode());
return sendSmsResponseDto;
}
public String makeSignature(Long time) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
String space = " "; // one space
String newLine = "\n"; // new line
String method = "POST"; // method
String url = "/sms/v2/services/"+applicationNaverSENS.getServiceid()+"/messages"; // url (include query string)
String timestamp = time.toString(); // current timestamp (epoch)
String accessKey = applicationNaverSENS.getAccesskey(); // access key id (from portal or Sub Account)
String secretKey = applicationNaverSENS.getSecretkey();
String message = new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timestamp)
.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;
}
}
테스트코드로 실행시켜봅니다.
@SpringBootTest
class ApplicationTests {
@Autowired
private SmsService smsService;
@Test
void sendSms() throws JsonProcessingException, ParseException, UnsupportedEncodingException, URISyntaxException, NoSuchAlgorithmException, InvalidKeyException {
smsService.sendSms("01012345678","문자 보내는거 너무 쉬워요!");
}
}
실제로 문자가 잘 오는 것을 확인하실 수 있습니다.