Kotlin 메일 인증 추가 1편 : 바로가기
Kotlin 메일 인증 추가 2편 : 바로가기
Kotlin 메일 인증 추가 3편 : 바로가기
이메일 회원 가입시 사용 할 메일 인증을 추가했다.
개발 과정에서 여러 에러들이 표시됐었는데,
다행히 빠르게 문제를 해결할 수 있었다.
spring:
mail:
host: smtp.gmail.com
port: 587
username: [your-google email]
password: [your-password]
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
auth-code-expiration-millis: 1800000 # 30 * 60 * 1000 == 30분
properties:
mail:
smtp:
auth : true
timeout : 5000
starttls:
enable : true
내가 기존에 입력했던건 아래와 같다.
properties 위쪽은 다 똑같았는데, 메일 발송이 안돼서 다시 찾아보았다.
반드시 설정해 줘야하는 부분이 있는듯 하다.
주요 변동사항 :
spring.mail.properties.mail.smtp.auth.starttls.required=true
아마 required 설정이 주요 원인이 아니었을까 하는 생각이 든다.
나머지는 타임아웃 관련이니 직접적인 영향을 준 것 같지는 않다.
implementation("org.springframework.boot:spring-boot-starter-mail")
spring-boot-starter-mail 을 추가해 준다.
준비가 끝났다.
fun getRandomString(length: Int) : String {
val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return (1..length)
.map { charset.random() }
.joinToString("")
}
먼저, 임의의 String을 구하기 위해 위와 같은 코드를 작성했다.
charset에 A부터 9까지의 모든 문자열을 넣고
그 중에서 내가 원하는 숫자만큼 갖다 붙였다.
UUID에서 가져오는 것도 좋은 방법일 것 같다.
fun getRandomString(length: Int) : String {
val UUID = UUID.randomUUID().toString()
return UUID.substring(0,length)
UUID는 36자리가 생성되니 굳이 0부터가 아니어도 괜찮다.
어떤 방법이든 유저에게 보낼 인증 코드를 생성했다.
@PostMapping("/email")
fun sendMail(
@RequestParam email : String
): ResponseEntity<SendMailResponse> {
return ResponseEntity
.status(HttpStatus.OK)
.body(userService.sendMail(email))
}
정말 무엇이 문제인지 모르겠지만, SendMailRequest를 생성해서
@RequestBody sendMailRequest:SendMailRequest
로 작성하였을 때
아래와 같은 에러가 표시됐다.
2024-02-01T00:01:02.170+09:00 WARN 46872 --- [io-8080-exec-10] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of -.domain.user.dto.request.SendMailRequest (although at least one Creator exists): cannot deserialize from Object value (no delegate- or property-based Creator)]
인스턴스를 구성할 수 없다는 에러였는데, Swagger와 PostMan에서 JSON 형식으로
요청을 보냈으나, 이를 Controller에서 읽지 못하는 문제가 발생했다.
문제를 해결하지 못해 email을 직접 받으니 문제가 해결되었다.
사실 요구하는 정보가 email밖에 없으므로 큰 문제가 되지 않는다.
override fun sendMail(email : String): SendMailResponse {
//인증 번호 만들기
val length = 6
val randomString = getRandomString(length)
//이메일 발송하기
val message = javaMailSender.createMimeMessage()
val helper = MimeMessageHelper(message)
helper.setTo(email) // 수신자 email
helper.setSubject("메일 제목")
helper.setText("인증 코드 : $randomString")
helper.setFrom("your Email")
javaMailSender.send(message)
mailRepository.save(
MailEntity(
email = email,
authcode = randomString
)
)
return SendMailResponse(message = "메일 발송 완료")
}
length에는 내가 원하는 길이를 입력하면 된다.
(6을 입력하면 인증 코드는 6자리가 됨)
Swagger에서 이메일을 입력한 뒤, 메일을 발송해 보았다.
정상적으로 발송되었다는 message를 응답받았다.
메일도 정상적으로 발송된 점을 확인할 수 있었다.
발송된 인증코드는 정상적으로 DB에 저장되었을까 ?
내가 보낸 인증 번호와, DB에 저장된 인증 번호가 동일하다.
이를 통해 email과 authcode를 비교하여 유저 인증이 가능하다.
GetRandomString과 sendMail Function은 Utillity Class를 만들어서 관리할 수 있다.
현재는 Service에 구현이 된 상태인데, 서비스 클래스의 복잡성을 줄이고
코드의 재사용성을 높일 수 있다.
Service에서는 Utillity.sendMail(email) 단 한 줄로 처리하도록 개선할 예정이다.
예를 들어 인증 코드가 a@gmail.com 에 3번 보내졌다면,
해당 이메일이 회원가입에 성공하였을 때 a@gmail.com 에 보낸 인증 코드를 삭제해야 한다.
db에 필요 없는 더미 데이터를 남겨둘 필요가 없기 때문이다.
회원가입에 실패하였을 때 주기적으로 DB를 삭제할 필요가 있다.
Spring에서 제공하는 스케쥴러를 이용할 수 있다.
매일 00:00에 2일 전 데이터를 삭제해야 될 것이다.
1일 전 데이터를 삭제할 경우 전 날 23:57에 보낸 사람은
유효 기간인 5분이 지나기 전에 DB에서 데이터가 삭제될 수 있다.
데이터를 휘발시킬 수 있는 Redis를 사용하면 더 좋지 않을까?
아직 사용법을 몰라 당장에는 사용할 수 없지만, 공부해서 붙여 볼 예정이다.
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: Unable to commit against JDBC Connection] with root cause
org.postgresql.util.PSQLException: ERROR: prepared statement "S_1" does not exist
이미 메일을 보낸 사람에게 다시 메일을 보내면 위와 같은 에러 코드와 함께 메일 발송이 안된다.
DB와 커뮤니케이션 중에 문제가 발생한 것으로 보이는데,
자세한 원인은 모르겠다.
따로 발신하지 않았는데, 약 8분정도 지난 뒤에 갑자기 인증 메일이 도착했다.
문제는 뒤늦게 온 이메일에 담긴 인증코드는 DB에 저장되지 않았다.
구글 SMTP의 문제일 수도 있다는 생각이 든다.