Post

Cool SMS 로 문자 인증 구현하기

문자 인증을 구현해보고 싶은데, 사업자 등록을 하지 않았다면? cool SMS 를 사용해보세요.

1. 준비하기

스타벅스 클론코딩을 진행하던 중, 문자 인증을 구현해야 하는 상황이 생겼습니다. 스타벅스 어플의 경우, 회원가입이나 유저 정보를 바꿀 때 문자 인증을 통해 유저 인증을 합니다. 하지만, 따로 사업장을 운영하는것이 아니라 mocking 을 하는 것이기 때문에 Cool SMS 를 사용하게 되었습니다.

1.1. Cool SMS 가입하기

먼저, cool sms 사이트 에 들어가서 회원 가입을 해주세요.

1.2. apikey 와 apiSecretKey 발급하기

  • 대시보드의 개발/연동 메뉴로 가서 apiKey 와 apiSecretKey 를 발급받아 주세요. 발급받은 키는 application.yml 에 아래와 같이 적어주세요.
  • sendNumber 를 넣지 않으면 오류가 나타나므로 이 점 주의해 주세요. 이때 sendNumber 는 실제 존재하는 휴대폰 번호 여야 합니다.
coolsms:
  api:
    key: ${COOLSMS_KEY}
    secret: ${COOLSMS_SECRET_KEY}

sendNumber: ${SEND_NUMBER}



2. 문자 인증 구현하기

2.1. SMSMessageDTO.java

기본적으로 coolSMS 는 유저 이름과 핸드폰 번호를 받습니다.

1
public record SMSMessageDTO(String username, String phoneNumber) {}

SMSVerificationDTO.java

인증번호가 발급된 후, 인증번호를 받아올 DTO 입니다.

1
2
3
4
5
6
7
8
9
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SMSVerificationDTO {
    private String username;
    private String phoneNumber;
    private String verificationCode;
}

2.2. SMSMessageUtil.java

저의 경우, {핸드폰 번호:인증 번호} 쌍을 레디스에 저장하였습니다.

  • 인증 DTO 가 왔을 때, 유저를 확인하기 위해서 입니다.
  • 레디스를 사용하므로써 데이터의 유효 기간을 설정할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
@Component
@RequiredArgsConstructor
@Slf4j
public class SMSMessageUtil {
    @Value("${coolsms.api.key}")
    private String apikey;

    @Value("${coolsms.api.secret}")
    private String apiSecretKey;

    @Value("${sendNumber}")
    private String sender;

    private DefaultMessageService messageService;
    private final RedisUtil redisUtil;
    private final UserRepository userRepository;

    @PostConstruct
    private void init() {
        this.messageService = NurigoApp
                .INSTANCE
                .initialize(apikey, apiSecretKey, "https://api.coolsms.co.kr");
    }

    public void verificationUser(SMSMessageDTO smsMessageDTO){
        String username = smsMessageDTO.username();
        String phoneNumber = smsMessageDTO.phoneNumber();

        Optional<User> user = userRepository.findUserByUsernameAndPhoneNumber(username, phoneNumber);
        if(user.isPresent()) {
            throw new EntityExistsException();
        }
    }

    public SingleMessageSentResponse sendMessage(String sendTo, String verificationCode) {
        Message message = new Message();
        message.setFrom(sender);
        message.setTo(sendTo);
        message.setText("[스타벅스] 인증번호는 " + verificationCode + " 입니다. 정확히 입력해주세요.");

        SingleMessageSentResponse response = this.messageService
                .sendOne(new SingleMessageSendingRequest(message));

        log.info("======================================================");
        log.info("SMSMessage sent to: " + sendTo);
        log.info("======================================================");

        return response;
    }

    public String generateVerificationCode() {
        Random random = new Random();
        int randomNumber = 100000 + random.nextInt(900000);

        return String.valueOf(randomNumber);
    }

    public void saveDataForCheckUser(String phoneNumber, String verificationCode) {
        redisUtil.setDataWithExpire(phoneNumber, verificationCode, Duration.ofMinutes(5));
        log.info("======================================================");
        log.info("CheckRedisKeyValue: " + redisUtil.getData(phoneNumber));
        log.info("======================================================");
    }
}

2.3. SMSMessageService.java

이 서비스에는 아래와 같은 메서드를 만들어 주었습니다.

  • 회원가입 전, 인증 번호를 전송하는 메서드
  • 회원가입 전, 받은 인증 번호와 보낸 인증 번호가 일치하는지 확인하는 메서드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
@RequiredArgsConstructor
public class SMSMessageService {
    private final SMSMessageUtil smsMessageUtil;
    private final RedisUtil redisUtil;

    public void getSMSVerificationBeforeSignUp(SMSMessageDTO smsMessageDTO){
        smsMessageUtil.verificationUser(smsMessageDTO);

        String verificationCode = smsMessageUtil.generateVerificationCode();
        String phoneNumber = smsMessageDTO.phoneNumber();

        smsMessageUtil.sendMessage(phoneNumber, verificationCode);
        smsMessageUtil.saveDataForCheckUser(phoneNumber, verificationCode);
    }

    public void checkUserUsingVerificationCode(String phoneNumber, String verificationCode) throws BadRequestException {
        String redisValue = redisUtil.getData(phoneNumber);
        if (!redisValue.equals(verificationCode)) {
          throw new BadRequestException();
        }
    }
}

2.4. SMSMessageController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@RestController
@RequestMapping(value = "/api/v1/auth/sms")
@RequiredArgsConstructor
public class SMSController {
    private final SMSMessageService smsMessageService;

    @PostMapping(value = "/send")
    public ResponseEntity<?> sendSMSForVerification(@RequestBody @Valid SMSMessageDTO smsMessageDTO) throws Exception {
        smsMessageService.getSMSVerificationBeforeSignUp(smsMessageDTO);
        return ResponseEntity.ok(smsMessageDTO);
    }

    @PostMapping(value = "/check")
    public ResponseEntity<?> checkUserUsingVerificationCode(@RequestBody SMSVerificationDTO smsVerificationDTO) throws Exception {
        smsMessageService.checkUserUsingVerificationCode(smsVerificationDTO.getPhoneNumber()
                , smsVerificationDTO.getVerificationCode());

        return ResponseEntity.ok(smsVerificationDTO);
    }
}



3. 결과

위와 같이 구현한 후, /api/v1/auth/sms 에 알맞은 Request 를 보내면 yml 에 등록한 번호에서 아래와 같이 문자가 옵니다.

This post is licensed under CC BY 4.0 by the author.