본문 바로가기

서버/SprintBoot
[스프링부트(Spring Boot)] sms 본인인증 ( Vue + Spring Boot )

회원가입을 할 때 사용하는 본인인증에는 sms, 이메일, pass 등등 다양한 방법이 존재한다.

프로젝트를 진행하면서 이번에는 sms 인증을 도전해 보았다.

찾아보니 coolsms를 많이 사용하는 것 같던데,, 익숙한 네이버를 선택했다.


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


회원 가입을 하고, 결제수단을 등록해야 한다.

그래야 우리가 사용할 sms 서비스 이용이 가능하다.

결제수단 관리는 마이페이지 > 결제관리 > 결제수단 관리를 들어가서 한다.


결제수단 등록까지 했으면,

https://console.ncloud.com/dashboard

콘솔 창으로 이동해서 이제 본격적인 작업을 해야 한다.

먼저 새 프로젝트를 생성해야 하는데, 생성하는 곳을 어떻게 들어올지 헤매는 경우가 있을 것이다.(바로 나^^)

Services에 "Simple & Easy Notification Service" 를 검색하면 된다. 

* 복사하실 분 복사하셔요..

Simple & Easy Notification Service

이제 프로젝트 생성!

서비스 Type은 SMS로, 이름과 설명은 자유롭게 적으면 된다.

생성한 후 서비스 ID는 사용해야 하니 잘 기록해 두세요.


이제 발신번호를 등록하러 gogo!


"Simple & Easy Notification Service" > "SMS" > "Calling Number" 로 들어간다.

프로젝트명 확인하고 인증해서 발신 번호 등록하면 끝!


API를 호출해서 사용하기 위해, API 키를 생성해야 한다.

console창이 아닌 메인 페이지의 마이페이지 > 계정 관리 > 인증키 관리 에서 생성할 수 있다.


인증키 관리가 안 떠요! 하시는 분들이 계실 수도 있다. 나도 처음에 그랬기 때문에,,

그렇다면 위에서 해야 하는 것들 중 어느 하나를 빼먹은 것.

차근차근 따라하면 인증키 관리 메뉴가 생겨있을 것이다.


'신규 API 인증키 생성' 버튼을 누르면 자동으로 생성된다. 

혹시 모르니 Access Key ID와 Secret Key는 잘 기록해 두세요!


이제 프로젝트로 와서, application.properties 파일에 관련 설정을 해주어야 한다.

참고로 개인정보이니 github 같은 곳에 올릴 때 주의하세요!

저는 gitignore에 추가해주었습니다.

*gitignore에 추가해줘도 바로 적용되지 않을 때가 있으니 다들 조심하세요... 

push한 이후라 reset하는 데 애먹었어요..


// Controller

저는 문자를 보낼 때 "인증번호를 입력해 주세요.\r\n" 다음에 인증코드를 붙여서 전송했어요.

자유롭게 원하는 문구로 바꾸세요~~ 

안 바꾸시려면 DTO를 바로 service에 넘겨주면 됩니다.

@RestController
public class SmsAuthController {

    @Autowired
    private SmsAuthService smsAuthService;

    @PostMapping("/auth")
    public ResponseEntity<ResultModel> sendSms(@RequestBody SmsMessageDTO messageDto) throws JsonProcessingException, RestClientException, URISyntaxException, NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {

        String message = "인증번호를 입력해 주세요.\r\n"+ messageDto.getContent();
        messageDto.setContent(message);

        return smsAuthService.sendSms(messageDto);
    }

// Service

Service같은 경우 interface이기 때문에 ServiceImpl 코드만 올릴게요 ㅎㅎ

위에서 application.properties에 naver-cloud-sms 관련 값들을 미리 설정해줬기 때문에, @Value로 불러오면 됩니다.

import org.springframework.beans.factory.annotation.Value;


이걸로 import 해주셔야 합니다!

저는 처음에 자동으로 lombok의 value가 import 돼서 에러를 잡는 데 힘들었답니다..후후

그리고 makeSignature() 같은 경우 API 공식 문서에 있는 함수를 사용하시면 됩니다!

https://api.ncloud-docs.com/docs/common-ncpapi

대신 그대로 가져다 쓰는 게 아니라 변경을 해야 할 부분들이 있습니다!!

다른 건 그대로 써도 되는데 method, url, timestamp, accessKey, secretKey는 바꿔주셔야 해요.

특히 method, url, timestamp 꼭 신경써 주세요!

제가 이걸 계속 놓쳐서 주구장창 401 에러가 났답니다..

makeSignature의 timestamp와 sendSms의 timestamp도 꼭 동일하게 맞춰주세요.

(어쩌다보니 저의 실수 일지네요....ㅎㅎ)

결과 return 하는 부분은 자유롭게 세팅하시면 됩니다.

저는 다른 Service들과 동일하게 맞춰준 거예요.

발송 결과 조회 메소드들도 따로 정의되어 있으니 필요하시면 api 문서 참고하셔서 추가해도 좋을 것 같습니다!

@Slf4j
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "naver-cloud-sms")
@Service
public class SmsAuthServiceImpl implements SmsAuthService {


    @Value("${naver-cloud-sms.access-key}")
    private String accessKey;

    @Value("${naver-cloud-sms.secret-key}")
    private String secretKey;

    @Value("${naver-cloud-sms.service-id}")
    private String serviceId;

    @Value("${naver-cloud-sms.sender-phone}")
    private String senderPhone;


    public String makeSignature(Long time) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
        String space = " ";
        String newLine = "\n";
        String method = "POST";
        String url = "/sms/v2/services/"+ this.serviceId+"/messages";
        String timestamp = time.toString();
        String accessKey = this.accessKey;
        String secretKey = this.secretKey;

        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;
    }

    public ResponseEntity<ResultModel> sendSms(SmsMessageDTO messageDto) throws JsonProcessingException, RestClientException, URISyntaxException, InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {

        ResultModel rModel = new ResultModel();

        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", accessKey);
        headers.set("x-ncp-apigw-signature-v2", makeSignature(time));

        List<SmsMessageDTO> messages = new ArrayList<>();
        messages.add(messageDto);

        SmsRequestDTO request = SmsRequestDTO.builder()
                                            .type("SMS")
                                            .contentType("COMM")
                                            .countryCode("82")
                                            .from(this.senderPhone)
                                            .content(messageDto.getContent())
                                            .messages(messages)
                                            .build();

        ObjectMapper objectMapper = new ObjectMapper();
        String body = objectMapper.writeValueAsString(request);
        HttpEntity<String> httpBody = new HttpEntity<>(body, headers);

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        SmsResponseDTO response = restTemplate.postForObject(new URI("https://sens.apigw.ntruss.com/sms/v2/services/"+ serviceId +"/messages"), httpBody, SmsResponseDTO.class);

        if(response.getStatusName().equals("success")) {
            rModel.setState(true);
            rModel.setMessage("인증번호 발송에 성공하였습니다.");
        } else {
            rModel.setState(false);
            rModel.setMessage("인증번호 발송에 실패하였습니다.");
        }

        return ResponseEntity.ok(rModel);
    }

}

혹시 3018 에러가 뜨시는 분들은 통신사 부가서비스로 발신번호 도용 방지를 가입하신 분들입니다!

네 제 얘기 맞아요..

해지하고 나면 바로 에러가 안 나는 건 아니고, 몇 시간 정도 지나야 해결되더라구요.

해지하고 다른 거 먼저 하고 계시는 걸 추천드립니다*^^* 

그리고 발신번호 같은 경우, ncloud에서 인증한 본인 번호로 입력되기 때문에, 만약 프로젝트 배포하실 분들은 과정이 조금 더 복잡해지실 것 같아요!

개인정보는 소중하니까요..

그리고 가장 중요한 금액!

50건까지는 무료로 가능하고, 50건을 초과하면 한 건당 9원이 부과됩니다.

자세한 건 홈페이지를 참고하세요 ㅎㅎ

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

 

NAVER CLOUD PLATFORM

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

www.ncloud.com


인증 실패/성공 화면 !

저는 인증 성공하면 입력 폼들을 수정 불가하게 막았습니다.

뿌-듯