하 일단 CoolSms, Springboot, Redis 이 세가지를 사용해서 문자 인증 구현하는 방법을 찾아봤는데 내용이 너무 별로 없어서 슬펐다 ..
coolsms의 경우 몇년 전 글들이랑 지금 사용하는 방법이랑 차이가 있었다!! DefaultMessageService의 sendOne을 이용해 문자를 한개 보낸다는 것이 가장 큰 차이인 듯!! 그래서 다른 플랫폼을 알아봤지만 내가 개발을 시작한 23년 11월에는 네이버 클라우드 플랫폼에서 Simple & Easy Notification Service 를 사용할 수 없었다.
그래서 nhn cloud의 sms 서비스를 이용하려고 했으나 신용카드를 등록해야했고, 나는 체크 카드 밖에 없어서 사용할 수가 없었다 ㅜㅜ 그래서 그냥 다시 coolsms를 이용하기로 결정했다.
코드의 큰 틀은 [Spring] 회원가입시 필요한 인증번호 관리 이 블로그를 보고 많이 참고했당 ㅋ [Spring] 문자 인증 구현하기 - coolSMS 이 블로그도 많이 참고함 ㅋ
이 두분의 블로그를 적절히 섞은 코드라고 해도 됨..ㅎㅎ
두 깃헙을 뒤져서 .. 한땀한땀 만든 코드 .. 아직 예외처리가 완벽하지는 않지만 추후 올릴예정입니당 !!!
redis:
host: localhost
port: 6379
repositories:
enabled: false
coolsms:
apiKey: ${내가 발급받은 키}
apiSecret: ${내가 발급받은 secret키}
senderNumber: 보내는 폰 번호
yml에서 senderNumber 진짜 중요함!!
없는 번호에서 발신은 안되기 때문에 꼭 있는 번호로 하기!! (이걸로 시간 엄청 잡아먹음 ㅜㅜ)
@EnableRedisHttpSession
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private int redisPort;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
return new LettuceConnectionFactory(redisHost, redisPort);
}
@Bean
public StringRedisTemplate stringRedisTemplate() {
StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory());
return stringRedisTemplate;
}
}
@RequiredArgsConstructor
@Repository
public class SmsCertificationDao {
private final String PREFIX = "sms:";
private final int LIMIT_TIME = 3 * 60;
private final StringRedisTemplate redisTemplate;
public void createSmsCertification(String phone, String certificationNumber) {
redisTemplate.opsForValue()
.set(PREFIX + phone, certificationNumber, Duration.ofSeconds(LIMIT_TIME));
}
public String getSmsCertification(String phone) {
return redisTemplate.opsForValue().get(PREFIX + phone);
}
public void removeSmsCertification(String phone) {
redisTemplate.delete(PREFIX + phone);
}
public boolean hasKey(String phone) {
return redisTemplate.hasKey(PREFIX + phone);
}
}
public class UserDto {
@Getter
public static class SmsCertificationRequest {
private String phone;
private String certificationNumber;
}
}
<SmsCertificationUtil.java>
@Component
public class SmsCertificationUtil {
@Value("${spring.coolsms.senderNumber}")
private String senderNumber;
@Value("${spring.coolsms.apiKey}")
private String apiKey;
@Value("${spring.coolsms.apiSecret}")
private String apiSecret;
DefaultMessageService messageService;
@PostConstruct
public void init() {
this.messageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecret, "https://api.coolsms.co.kr");
}
public SingleMessageSentResponse sendSms(String to, String verificationCode){
Message message = new Message();
message.setFrom(senderNumber);
message.setTo(to);
message.setText("[물막이] 본인 확인 인증번호는 "+verificationCode+"입니다.");
SingleMessageSentResponse response = this.messageService.sendOne(new SingleMessageSendingRequest(message));
System.out.println(response);
return response;
}
}
public interface UserService {
void sendSms(UserDto.SmsCertificationRequest requestDto);
void verifySms(UserDto.SmsCertificationRequest requestDto);
boolean isVerify(UserDto.SmsCertificationRequest requestDto);
}
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final SmsCertificationUtil smsUtil;
private final SmsCertificationDao smsCertificationDao;
public void sendSms(UserDto.SmsCertificationRequest requestDto){
String to = requestDto.getPhone();
int randomNumber = (int) (Math.random() * 9000) + 1000;
String certificationNumber = String.valueOf(randomNumber);
smsUtil.sendSms(to, certificationNumber);
smsCertificationDao.createSmsCertification(to,certificationNumber);
}
public void verifySms(UserDto.SmsCertificationRequest requestDto) {
if (isVerify(requestDto)) {
throw new CustomExceptions.SmsCertificationNumberMismatchException("인증번호가 일치하지 않습니다.");
}
smsCertificationDao.removeSmsCertification(requestDto.getPhone());
}
public boolean isVerify(UserDto.SmsCertificationRequest requestDto) {
return !(smsCertificationDao.hasKey(requestDto.getPhone()) &&
smsCertificationDao.getSmsCertification(requestDto.getPhone())
.equals(requestDto.getCertificationNumber()));
}
}
@RestController
@RequiredArgsConstructor
@RequestMapping("/sms-certification")
public class SmsCertificationController extends BaseController {
private final UserService userService;
@PostMapping("/send")
public ResponseEntity<?> sendSms(@RequestBody UserDto.SmsCertificationRequest requestDto) throws Exception {
try {
userService.sendSms(requestDto);
return new ResponseEntity(DefaultRes.res(StatusCode.OK, ResponseMessage.SMS_CERT_MESSAGE_SUCCESS), HttpStatus.OK);
} catch (CustomExceptions.Exception e) {
return handleApiException(e, HttpStatus.BAD_REQUEST);
}
}
//인증번호 확인
@PostMapping("/confirm")
public ResponseEntity<Void> SmsVerification(@RequestBody UserDto.SmsCertificationRequest requestDto) throws Exception{
try {
userService.verifySms(requestDto);
return new ResponseEntity(DefaultRes.res(StatusCode.OK, ResponseMessage.SMS_CERT_SUCCESS), HttpStatus.OK);
} catch (CustomExceptions.Exception e) {
return handleApiException(e, HttpStatus.BAD_REQUEST);
}
}
}
코드에 대한 설명은 나중에 붙여둠!!

인증까지 잘 되는 모습 !!
CustomExceptions 은 별도로 생성하신 예외처리 함수인가요?