2025. 2. 12. 14:34ㆍ개발자 과정/Kotlin
들어가기 앞서, 이분의 코드를 거의 따라했다.
https://akku-dev.tistory.com/127
스프링부트에서 SMS 본인 인증 구현하기 - 1. Twilio로 구현
현재 개발 중인 서비스에서 본인 인증 기능이 필요해졌다. 한번쯤 써봤을 PASS 본인인증 같은 기능이다. 내가 인증 대상이 될 때는 몰랐는데, 본인 인증 기능을 개발하려니 업체 선정에서부터 어
akku-dev.tistory.com
문자인증, 문자 전송 서비스를 만들어 보려 하는데, kotlin으로 작성된 글은 없는 것 같아 내가 올리고자 한다.
우선 어째서 twilio를 쓰는가 부터 알아보아야 한다.
장점
1. 비용
보통 문자서비스는 구독제 이거나, 건당 가격으로 비용이 들어간다. 하지만 twilio는 트라이얼 계정으로, $15 크레딧의 테스트 비용 혜택이 있다.
2. 세션관리
문자인증을 구현하려면, 자체 캐시 DB(redis등)를 우리 서비스 내부에서 처리해야 하지만, twilio는 자체 세션을 지원해 주기 때문에, 간단하게 api요청만 보내면 나머지는 twilio쪽에서 알아서 처리해 준다.
3. 서비스 구현의 간소화
코드 발급-DB저장-인증코드 검증-결과 반환 이라는 플로우를 오롯이 twilio에게 위임할 수 있다. 이로서 우리 서비스의 코드와 스코프는 유의미 하게 줄어드는 이점을 얻을 수 있다.
twilio에 계정을 등록하는 과정은 이미 구글에 넘쳐나기 때문에, 해당 과정은 생략하고 바로 구현으로 넘어간다.
구현
config
@Configuration
class TwilioConfig {
@Value("\${twilio.account-sid}")
private val accountSid: String? = null
@Value("\${twilio.auth-token}")
private val authToken: String? = null
@Value("\${twilio.service-sid}")
private val serviceSid: String? = null
@Value("\${twilio.from-phone}")
private val fromPhone:String?=null
@PostConstruct
fun init() {
Twilio.init(accountSid, authToken)
}
fun getServiceSid():String{
return serviceSid!!
}
fun getFromPhone():PhoneNumber{
return PhoneNumber(fromPhone!!)
}
}
문자인증과 문자전송은 구분된다. 문자인증은 twilio가 자체 세션과 번호로 진행을 하지만, 문자전송은 우리가 twilio에서 가상 번호를 발급 받고, 그것을 활용해야 한다.
token과 sid는 twilio에서 서비스를 등록하면 바로 찾을 수 있다. from-phone은 우리가 문자 메세지를 보낼때 활용할 가상 번호이다.
interface
interface TwilioComponent {
fun verification(userVerifyCodeRequestDto: UserVerifyCodeRequestDto): String
fun verificationCheck(userVerifyCheckRequestDto: UserVerifyCheckRequestDto): String
fun sendSms(phone: String, message:String)
}
이 세가지 함수만 있으면 기본적인 문자 서비스는 다 해결이 된다.
implement
@Component
class TwilioComponentImpl(
private val twilioConfig: TwilioConfig
): TwilioComponent {
// 인증 번호 요청
override fun verification(userVerifyCodeRequestDto: UserVerifyCodeRequestDto): String {
val e164FormatPhoneNumber: String = EncodeUtils.getE164FormatPhoneNumber(userVerifyCodeRequestDto.phone)
val verification: Verification = Verification.creator(
twilioConfig.getServiceSid(),
e164FormatPhoneNumber,
"sms"
).create()
println(verification.status)
return "인증 번호 요청 성공"
}
// 인증 번호 검증
override fun verificationCheck(userVerifyCheckRequestDto: UserVerifyCheckRequestDto): String {
val e164FormatPhoneNumber: String = EncodeUtils.getE164FormatPhoneNumber(userVerifyCheckRequestDto.phone)
try {
val verificationCheck = VerificationCheck.creator(
twilioConfig.getServiceSid()
)
.setTo(e164FormatPhoneNumber)
.setCode(userVerifyCheckRequestDto.code)
.create()
check(verificationCheck.valid)
} catch (e: IOException) {
throw IOException("발송 실패")
}catch (e: Exception) {
throw IllegalStateException("인증 실패")
}
return "인증 번호 검증 성공"
}
override fun sendSms(phone: String, message: String) {
val e164FormatPhoneNumber = PhoneNumber(EncodeUtils.getE164FormatPhoneNumber(phone))
try {
val sendMessage = Message.creator(
e164FormatPhoneNumber,
twilioConfig.getFromPhone(),
message
).create()
println(sendMessage.body)
} catch (e: Exception) {
throw IOException(e.message)
}
}
}
구현체는 함수 이름 그대로이니 어려울건 없다. 여기서 눈여겨 봐야 하는 것은, 문자인증에선 핸드폰 번호를 E164로 바꾸는 것이다. E164가 국제 표준 번호 표기법 같은데, 그런건 잘 모르겠고 그래서 쉽게 구현되는지가 더 궁금하니까 뭐~
EncodeUtils
object EncodeUtils {
fun getE164FormatPhoneNumber(number: String): String {
try {
val phoneNumberUtil = PhoneNumberUtil.getInstance()
val parsedPhoneNumber = phoneNumberUtil.parse(number, "KR")
return phoneNumberUtil.format(parsedPhoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164)
} catch (e: NumberParseException) {
throw RuntimeException(e)
}
}
}
유틸리티는 object로 빼기도 한다!
DTO
data class UserVerifyCheckRequestDto(
val phone:String,
val code:String
)
data class UserVerifyCodeRequestDto (
val phone: String
)
사용
여기까지 왔으면, 이제 우리 서비스에다가 interface를 붙이기만 하면 된다.
@Service
class AuthServiceImpl(
private val twilioComponent: TwilioComponent
):AuthService {
override fun twilioVerificationSend(userVerifyCodeRequestDto: UserVerifyCodeRequestDto): String {
return twilioComponent.verification(userVerifyCodeRequestDto)
}
override fun twilioVerificationCheck(userVerifyCheckRequestDto: UserVerifyCheckRequestDto): String {
return twilioComponent.verificationCheck(userVerifyCheckRequestDto)
}
}
이런식으로 authService에 붙이거나, 아예 외부인증 모듈을 만들어서 그곳에 의존시켜도 된다. 그리고 왠만하면 interface를 쓰자. 외부서비스인 만큼 의존성을 역전시키는건 중요하다 생각이 든다. 로봇 장난감에 무기는 쥐어주는 거지, 일체형이 되면 그건 로봇팔인가 로봇무기인가? 뭐, 그런 생각이니까 말이다.
twilioComponent.sendSms(phone,"내용")
이런식으로 사용자 번호와 메세지를 매개변수로 넘겨줘서 문자전송을 위임해버릴 수도 있다. 즉, 구축만 하면 아무곳에다가 불러와서 써도 돌아간다!
마무리
아, 그리고 트라이얼 계정이라 그런건지 확실하진 않으나, 문자 매세지에 한국어가 섞이면 전송이 제대로 되지 않는 문제가 있다. 아마 이것은 twilio의 언어지원 문제 같다.
'개발자 과정 > Kotlin' 카테고리의 다른 글
깃허브 파종기 MK.2 (리모트 컨트롤, 문자알림, 디스코드 웹훅) (0) | 2025.02.28 |
---|---|
깃허브 파종기를 만들어 보자! (0) | 2025.02.24 |
springboot기초(외전)-추상화는 곧 가독성이다. (0) | 2025.01.08 |
springboot기초(4)-멤버를 추가해보자 (3) | 2024.10.03 |
springboot기초(3)-스코어와 히스토리를 저장해보자 (1) | 2024.09.26 |