Spring에서 SMS 인증 기능 개발하기(feat. CoolSMS)

Minjae An·2024년 1월 26일
0

Spring ETC

목록 보기
7/8

개요

서비스를 이용하다 보면 SMS를 통해 고유한 코드를 보내고 이를 입력하여 인증 절차를 진행하는 경우가 있다. 이런 기능은 어떻게 개발하는지 알아보자. 이하 예제 코드는 CoolSMS 서비스를 이용하여 구성하였다.

참고로 국내 SMS API의 경우 건당 9~20원 정도의 가격이 형성되어 있다.

SMS API 사용을 위한 설정

CoolSMS 홈페이지에 들어가 서비스에 가입한 후 좌측 탭의 개발/연동 → API Key 관리에 들어가 어플리케이션에서 사용할 키를 생성해준다.

build.gradle

implementation 'net.nurigo:sdk:4.3.0'

CoolSMS에서 제공하는 Java SDK 의존성을 추가한다.

application.yml

spring:
	sms:
	    api-key: ${SMS_API_KEY}
	    api-secret: ${SMS_API_SECRET}
	    provider: https://api.coolsms.co.kr
	    sender: ${SMS_SENDER}

SMS SDK를 이용하기 위한 키, 시크릿 등의 설정을 YAML 설정 파일에 정의하고, 외부에서 값을 주입받도록 구성할 계획이다. 이에 따라 application.yml 을 위와 같이 작성해준다.

SmsService

package com.example.springallinoneproject.sms;

import com.example.springallinoneproject.api_payload.status_code.ErrorStatus;
import com.example.springallinoneproject.auth.VerificationCode;
import com.example.springallinoneproject.auth.VerificationCodeGenerator;
import com.example.springallinoneproject.auth.VerificationCodeRepository;
import com.example.springallinoneproject.exception.GeneralException;
import jakarta.annotation.PostConstruct;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import net.nurigo.sdk.NurigoApp;
import net.nurigo.sdk.message.model.Message;
import net.nurigo.sdk.message.request.SingleMessageSendingRequest;
import net.nurigo.sdk.message.service.DefaultMessageService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class SmsService {

    @Value("${spring.sms.api-key}")
    private String apiKey;
    @Value("${spring.sms.api-secret}")
    private String apiSecret;
    @Value("${spring.sms.provider}")
    private String smsProvider;
    @Value("${spring.sms.sender}")
    private String smsSender;

    private DefaultMessageService messageService;

    private final VerificationCodeRepository verificationCodeRepository;

    @PostConstruct
    public void init(){
        messageService = NurigoApp.INSTANCE.initialize(
                apiKey,
                apiSecret,
                smsProvider
        );
    }

    public void sendVerificationMessage(String to, LocalDateTime sentAt){
        Message message = new Message();
        message.setFrom(smsSender);
        message.setTo(to);

        VerificationCode verificationCode = VerificationCodeGenerator
                .generateVerificationCode(sentAt);
        verificationCodeRepository.save(verificationCode);

        String text = verificationCode.generateCodeMessage();
        message.setText(text);

        messageService.sendOne(new SingleMessageSendingRequest(message));
    }

    public void verifyCode(String code, LocalDateTime verifiedAt){
        VerificationCode verificationCode = verificationCodeRepository.findByCode(code)
                .orElseThrow(() -> new GeneralException(ErrorStatus._VERIFICATION_CODE_NOT_FOUND));

        if(verificationCode.isExpired(verifiedAt)){
            throw new GeneralException(ErrorStatus._VERIFICATION_CODE_EXPIRED);
        }

        verificationCodeRepository.remove(verificationCode);
    }
}

CoolSMS SDK의 경우 DefaultMessageService 클래스를 이용해 SMS와 관련된 기본적인 기능들을 이용할 수 있게 해준다. 위 예제 코드에서는 @PostConstruct 를 이용하여 해당 클래스를 초기화해주었지만 여러 클래스에서 사용된다면 빈 등록과정에서 초기화해주고 빈으로 사용하는 것이 더 적절해보인다. 인증 코드를 SMS로 전송하는 작업은 다음 절차를 따른다.

  1. CoolSMS SDK에서 제공하는 Message 객체를 이용해 수신, 발신자를 설정한다.
    • 참고로 CoolSMS를 이용하기 위해서는 - 을 포함하지 않은 01011112222 와 같은 형태로 번호를 인자로 사용해야 한다.
  2. VerificationCode 를 생성하고 검증에 사용할 수 있도록 저장한다.
  3. 인증 코드 메시지를 생성하여 Message 객체에 설정해준다.
  4. 메시지를 전송한다.

SmsController

package com.example.springallinoneproject.sms;

import com.example.springallinoneproject.api_payload.CommonResponse;
import com.example.springallinoneproject.api_payload.status_code.SuccessStatus;
import com.example.springallinoneproject.sms.dto.SmsRequest.PhoneNumberForVerificationRequest;
import com.example.springallinoneproject.sms.dto.SmsRequest.VerificationCodeRequest;
import java.time.LocalDateTime;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class SmsController {
    private final SmsService smsService;

    @PostMapping("/verify-phone-number")
    public CommonResponse<Void>
    getPhoneNumberForVerification(@RequestBody PhoneNumberForVerificationRequest request) {
        LocalDateTime sentAt = LocalDateTime.now();
        smsService.sendVerificationMessage(request.getPhoneNumber(), sentAt);
        return CommonResponse.of(SuccessStatus._ACCEPTED, null);
    }

    @PostMapping("/phone-number/verification-code")
    public CommonResponse<String> verificationByCode(@RequestBody VerificationCodeRequest request) {
        LocalDateTime verifiedAt = LocalDateTime.now();
        smsService.verifyCode(request.getCode(), verifiedAt);
        return CommonResponse.ok("정상 인증 되었습니다.");
    }
}

컨트롤러에는 인증 코드를 전송할 휴대폰 번호를 받는 API와 인증 코드를 검증할 API를 구성하였다.

정상 동작 확인


Postman을 이용해 인증 코드 메시지를 받을 번호를 요청 바디에 포함하여 요청해보자.

인증 코드가 잘 전송되는 것을 확인할 수 있다.

전체 코드

https://github.com/Minjae-An/spring-all-in-one/tree/feat/%2317-mobile-authentication

참고

profile
내가 쓴 코드가 남의 고통이 되지 않도록 하자

0개의 댓글