Kotlin 메일 인증 추가 1편 : 바로가기
Kotlin 메일 인증 추가 2편 : 바로가기
Kotlin 메일 인증 추가 3편 : 바로가기
@Component
class MailUtility(
val javaMailSender : JavaMailSender,
val mailRepository: MailRepository,
) {
fun getRandomString() : String {
val length = 6
val charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return (1..length)
.map { charset.random() }
.joinToString("")
}
fun sendMail(email: String) : String {
//인증 번호 만들기
val randomString = getRandomString()
//이메일 발송하기
val message = javaMailSender.createMimeMessage()
val helper = MimeMessageHelper(message)
helper.setTo(email)
helper.setSubject("gogoCard 이메일 인증")
helper.setText("인증 코드 : $randomString")
helper.setFrom("{email}")
javaMailSender.send(message)
return randomString
}
}
기존에 Entity와 Service에 존재했던 코드들은 그 역할에 맞지 않는 곳에
구현이 되어 있었다.
Utility라는 클래스를 생성하여, 여기서 구현했다.
override fun sendMail(email: String): SendMailResponse {
val randomString = mailUtility.sendMail(email)
mailRepository.save(
MailEntity(
email = email,
authcode = randomString
)
)
return SendMailResponse(message = "메일 발송 완료")
}
개선된 service는 위와 같다.
1에서 하기로 한 것
예를 들어 인증 코드가 a@gmail.com 에 3번 보내졌다면,
해당 이메일이 회원가입에 성공하였을 때 a@gmail.com 에 보낸 인증 코드를 삭제해야 한다.
db에 필요 없는 더미 데이터를 남겨둘 필요가 없기 때문이다.
@Transactional
override fun signUp(request: CreateUserRequest): UserResponse {
val getAuthCode = mailRepository.findByEmail(request.email)?.authcode
?: throw ModelNotFoundException("Email", null)
if (request.authcode != getAuthCode) {
throw IllegalStateException("인증 코드가 틀렸습니다.")
} else (
if (userRepository.existsByEmail(request.email)) {
throw IllegalStateException("이미 존재하는 이메일입니다.")
})
mailRepository.deleteByEmail(request.email)
return userRepository.save(
UserEntity(
userName = request.userName,
email = request.email,
password = passwordEncoder.encode(request.password),
role = request.role)
).toResponse()
}
1) 먼저, db에서 email을 찾고 db에서 authcode를 찾아온다.
2) 클라이언트에서 입력한 authcode와 db의 authcode를 비교한다.
3) 동일한 email이 있는지 확인한다.
4) db에 저장된 email을 삭제한다. (같은 이메일은 모두 삭제함)
Query did not return a unique result: 4 results were returned
에러가 표시된다.
이유가 뭘까?
여기보면 하나의 이메일에 4개의 authcode가 저장된 상태이기 때문이다.
findByEmail
은 이메일 하나만을 비교한다.
아래와 같이 수정했다.
val getAuthCode = mailRepository.findAllByEmail(request.email)
?: throw ModelNotFoundException("Email", null)
if (getAuthCode.none { it.authcode == request.authcode }) {
throw IllegalStateException("인증 코드가 틀렸습니다.")
}
if (userRepository.existsByEmail(request.email)) {
throw IllegalStateException("이미 존재하는 이메일입니다.")
}
그리고 회원가입을 하면
같은 메일에서 발송 된 모든 인증코드가 삭제된 내용을 확인할 수 있다 !
에러가 잘 표시 된다.
가장 최근의 인증코드만 유효한 것이 아니라, 발급된 모든 인증코드가 유효한 상태가 된다.
발급된 모든 인증코드가 인증되면 안되나..? 라는 생각도 들긴 하다.
Kotlin의 none 함수는 컬렉션의 모든 요소가 주어진 람다식을 만족하는지 검사하는 함수이다.
컬렉션의 모든 요소 중 만족하지 않을 경우 true, 만족할 경우 false를 반환한다.
getAuthCode.none { it.authcode == request.authcode }
는 getAuthCode 컬렉션의
모든 요소가 authcode가 request.authcode와 같은게 있는지 검사한다.
즉, it.authcode가 request.authcode와 같은 요소가 없으면 true, 있으면 false를 반환한다.
1에서 업로드한 내용 중 메일 발송이 안되던 점은 트랜잭션 어노테이션을 삭제한 뒤
문제가 해결되었다.
아마 같은 요청이 반복되었을 때, 하나의 요청으로 처리한 것이 아닐까 하는 추측이다.
어노테이션을 삭제하고 1개만 노출되던 문제는 해결했다.
하지만 여전히 여러 번 시도했을 때 일부 메일은 DB에 저장되지 않는 현상은 여전하다.