티스토리 뷰

최근 신입으로 맡게된 프로젝트에서 사용하게 될 문자인증 서비스를 위해 문자 관련한 api를 찾아보았다.

제공하는 라이브러리를 조사해보니

 

coolsms

https://coolsms.co.kr/

 

세상에서 가장 안정적이고 빠른 메시지 발송 플랫폼 - 쿨에스엠에스

손쉬운 결제 전용계좌, 신용카드, 계좌이체 등 국내 결제 뿐만 아니라 해용신용카드로 한번의 카드번호 등록으로 자동충전까지 지원합니다. 전용계좌, 신용카드, 계좌이체 등 다양한 결제 방식

coolsms.co.kr

 

NHN Cloud

https://docs.nhncloud.com/ko/Notification/SMS/ko/api-guide/

 

API v3.0 가이드 - NHN Cloud 사용자 가이드

Notification > SMS > API v3.0 Guide v3.0 API 소개 v2.4와 달라진 사항 시크릿 키가 도입되었습니다. v3.0 API 호출 시에는 헤더에 시크릿 키를 추가해야 성공하게 됩니다. 대량 발송 요청을 조회할 수 있는 API

docs.nhncloud.com

 

Naver Cloud

https://www.ncloud.com/

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

등이 있고

 

찾아보면 더 많이 되겠지만 이 안에서 사용할 것을 고려하기로 했다.

 

코드를 구현하는 것 자체는 api문서가 각각 너무 잘나와있고 구현하기에 어렵지 않다고 생각했다.

 

그렇다면 개발자로써 무엇을 생각하여 3개의 라이브러리 중 하나를 골라야할까

여러 이유가 있겠지만 몇가지를 추려서 생각해보았다.

 

가격,  사용법,  추가과정

 

1. 가격

coolsms 가격

https://coolsms.zendesk.com/hc/ko/articles/115001071551-%EB%AC%B8%EC%9E%90%ED%83%80%EC%9E%85%EB%B3%84-%EB%B9%84%EC%9A%A9

 

기본적으로 일반문자(sms)를 사용한다는 가정하에 건당 13.67 ~ 18.18원이 나온다.

 

 

nhn cloud

https://www.nhncloud.com/kr/pricing/m-content?c=Notification&s=SMS 

 

NHN Cloud : 유연하게 안전하게 비즈니스의 힘이 되는 통합 클라우드 서비스

안정적이고 유연한 기업용 클라우드 컴퓨팅 서비스, 오픈스택 기반의 개방성과 신뢰로 고객사의 비즈니스에 힘이 되는 NHN Cloud

www.nhncloud.com

수신 1건당 9.9원이 나온다.

 

 

naver cloud platform

https://www.ncloud.com/product/applicationService/sens

 

NAVER CLOUD PLATFORM

cloud computing services for corporations, IaaS, PaaS, SaaS, with Global region and Security Technology Certification

www.ncloud.com

50건 이하 건당 무료! 50건 이상 9원이 나오게 된다.

 

테스트로 사용해도 충분할 50건의 무료문자도 주는걸 보니

가격적으로는 naver를 사용하는게 옳다고 본다.

(물론 다른 플랫폼에서도 무료 건수를 주는 걸로 알고있지만 네이버 만큼 주는건 아닌걸로 안다.)

 

 

2. 사용법

실제로 내가 회사에 들어온 후 추가개발을 맡게 된 레거시 프로젝트들은 전부 coolsms로 구현되어 있었고

이번에 혼자 개발에 들어가니 여러가지를 조사하며 선임개발자에게 물어봤다.

 

왜 coolsms 사용하셨어요? 가격이 비싸지 않나요?

사용하기 되게 쉬워서요.

말 그대로다. (물론 그 이후에 추가적인 이유를 알려주었다. 기획서 등..)

 

https://github.com/coolsms/coolsms-java-examples/tree/main

coolsms를 보면 별다른 설정 없이 build.gradle 추가하고 깃허브에 있는 코드(단일 메시지 발송 예제)를 그대로 사용하면 끝이다.

발급받은 키 하나만 적으면 바로 메세지를 보낼 수 있다는 것이다..

 

아 물론 다른 api가 그렇다고 해서 어렵다? 그건 동의할 수 없다.

조금 암호화된 과정을 추가해주어서 그렇게 보일 수 있지만 전혀 문제될 부분이 아니었다.

 

그러므로 사용법은 api를 잘 보면 되므로 문제될 것이 없다.

 

 

3. 추가과정

별다른건 아니다. 추가적인 확장성? 등을 생각해보았다.

현재 다니는 SI 업체로써 주어진 상황에만 맞추면 되는게 아닌

추후 나의 서비스를 구축 할때의 상황을 고려해보고자 했다.

 

네이버 클라우드 플랫폼을 사용하게 된다면 네이버 클라우드 내의 서비스를 이용할 확률

즉, 내가 지도앱을 생각할 때도 굳이 카카오 지도 등을 생각하지 않고

이미 계정을 갖고 있는 네이버 지도를 사용하지 않을까? 하는 생각말이다.

 

실제 서비스 회사에서의 고려사항은 어떨지 모르지만 여러 라이브러리를 가져와 사용하는 것 보다.

하나의 안정적인 라이브러리를 사용하는게 안정성에서도 옳지 않을까? 하는 생각을 하게 되었다.

 

 

자.

잡설은 길었다. 코드를 구현해보자.

 

 

환경설정

  • Java 17
  • SpringBoot 3.1.3

네이버에서 준비해야 할건 3개다.

accessKey, secretKey, serviceId 어렵지 않고 구글로 충분히 찾아볼 수 있으니 생략하려 한다.

public class NaverApi {

	public static final String ACCESS_KEY = "발급받은 accessKey";
	public static final String SECRET_KEY = "발급받은 secretKey";
	public static final String SERVICE_ID = "발급받은 serviceId";
	public static final String SENDER_PHONE = "보내려는 번호";
    
}

@Value를 통해 application.yml에서 가져오는 방법이 있겠지만 constant 패키지 내부에 보관하는 방식으로

api에서 사용할 key 들을 보관하기로 했다.

 

 

Controller

@RestController
@RequiredArgsConstructor
public class MessageController {

	private final MessageService messageService;

	@PostMapping("/api/sms")
	public String testMethod(@RequestBody MessageRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException, InvalidKeyException, JsonProcessingException {

		return messageService.sendSms(request);
	}

}

MessageService를 주입받아 sendSms 메서드를 호출하기만 하면 된다.

 

MessageRequest의 경우

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class MessageRequest {

	private String to;

}

어노테이션을 주렁주렁 달았지만 필요없는 것이 많다.

 

 

Service

@Slf4j
@Service
public class MessageService {

	/**
	 * 인증번호 구현
	 * @param request
	 * @return
	 */
	public String sendSms(MessageRequest request) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException, JsonProcessingException, URISyntaxException {

		String sixRandomNumber = makeRandomNumber();
		Long time = System.currentTimeMillis();

		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_JSON);
		headers.set("x-ncp-apigw-timestamp", time.toString());
		headers.set("x-ncp-iam-access-key", NaverApi.ACCESS_KEY);
		headers.set("x-ncp-apigw-signature-v2", makeSignature(time));

		List<MessageRequest> messages = new ArrayList<>();
		messages.add(request);

		SmsRequest smsRequest = SmsRequest.builder()
			.type("SMS")
			.contentType("COMM")
			.countryCode("82")
			.from(NaverApi.SENDER_PHONE)
			.content("인증번호는 [" + sixRandomNumber + "] 입니다.")
			.messages(messages)
			.build();

		ObjectMapper objectMapper = new ObjectMapper();
		String body = objectMapper.writeValueAsString(smsRequest);

		HttpEntity<String> httpBody = new HttpEntity<>(body, headers);
		RestTemplate restTemplate = new RestTemplate();
		restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

		SmsResponse response =  restTemplate.postForObject(new URI("https://sens.apigw.ntruss.com/sms/v2/services/" + NaverApi.SERVICE_ID + "/messages"), httpBody, SmsResponse.class);

		if (Objects.requireNonNull(response).getStatusCode().equals("202")) {
			return sixRandomNumber;
		}
		if (response.getStatusName().equals("fail")) {
			return response.getStatusName();
		}
		return sixRandomNumber;
	}

	/**
	 * 난수 생성 메서드
	 * @return
	 */
	private String makeRandomNumber() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 6; i++) {
			sb.append((int) (Math.random() * 9));
		}
		return sb.toString();
	}

	/**
	 * sms signature 생성
	 * @param time
	 * @return
	 */
	public String makeSignature(Long time) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
		String space = " ";					// one space
		String newLine = "\n";					// new line
		String method = "POST";					// method
		String url = "/sms/v2/services/" + NaverApi.SERVICE_ID + "/messages";	// url (include query string)
		String timestamp = time.toString();			// current timestamp (epoch)
		String accessKey = NaverApi.ACCESS_KEY;			// access key id (from portal or Sub Account)
		String secretKey = NaverApi.SECRET_KEY;

		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"));

		return Base64.encodeBase64String(rawHmac);
	}

}

중요한 부분은 sms를 보낼때 http 호출하기 위해서 헤더에 signature값을 만들어 보내준다는 것이다.

그 부분이 makeSignature라는 메서드가 되고 그 반환값을 헤더에 넣어주어 RestTemplate으로 네이버 api 호출을 한다.

 

그 안에 makeRandomNumber은 인증번호를 보낼 때 받게 되는 6개의 무작위 숫자 알고리즘이다.

 

SmsRequest, SmsResponse

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SmsRequest {

	private String type;
	private String contentType;
	private String countryCode;
	private String from;
	private String content;
	private List<MessageRequest> messages;

}


@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SmsResponse {

	private String requestId;
	private LocalDateTime requestTime;
	private String statusCode;
	private String statusName;
}

사용할 DTO 클래스

 

 

Test

@SpringBootTest
class MessageServiceTest {

	@Autowired
	MessageService messageService;

	@Test
	@DisplayName("sendSms 단위 테스트")
	void sendSms() throws UnsupportedEncodingException, NoSuchAlgorithmException, URISyntaxException, InvalidKeyException, JsonProcessingException {
		//given
		MessageRequest request = MessageRequest.builder()
			.to("01022745320").
			build();

		//when
		String response = messageService.sendSms(request);

		//then
		assertNotEquals("fail", response);
	}
}

테스트 코드를 실행해보고 메시지를 받았다.

 

 

사실 api 대로 진행하기만 하면 문제될 부분이 없어 난이도 자체는 어렵지 않았다.

예외처리를 추가해주면서 프론트엔드와 소통하면 될 듯 하다.

'Backend > SpringBoot' 카테고리의 다른 글

디자인패턴에 대해서  (1) 2023.12.11
토스페이먼츠 결제모듈 구현  (0) 2023.12.01
Swagger Opendoc API  (0) 2023.07.18
Jwt + SpringSecurity + Mybatis 구현  (0) 2023.07.06
SpringBoot 동적인(?) 파일 수정  (0) 2023.05.15
Comments