-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMailService.java
More file actions
111 lines (94 loc) · 3.8 KB
/
MailService.java
File metadata and controls
111 lines (94 loc) · 3.8 KB
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
package com.mycom.socket.auth.service;
import com.mycom.socket.auth.service.data.VerificationData;
import com.mycom.socket.global.exception.BaseException;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.security.SecureRandom;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
@RequiredArgsConstructor
public class MailService {
private final JavaMailSender javaMailSender;
private final RateLimiter rateLimiter; // 인증 번호 요청 제한
private final Map<String, VerificationData> verificationDataMap = new ConcurrentHashMap<>();
@Value("${spring.mail.username}")
private String senderEmail;
/**
* 6자리 난수 인증번호 생성
* SecureRandom 사용하여 보안성 향상
* @return 100000~999999 범위의 인증번호
*/
private String createVerificationCode() {
// Math.random()은 예측 가능한 난수를 생성할 수 있어 보안에 취약
// SecureRandom은 암호학적으로 안전한 난수를 생성하므로 인증번호 생성에 더 적합
SecureRandom secureRandom = new SecureRandom();
return String.format("%06d", secureRandom.nextInt(1000000));
}
/**
* 인증메일 생성
* @param mail 수신자 이메일 주소
* @return 생성된 인증메일
*/
public MimeMessage createMail(String mail, String verificationCode) {
MimeMessage message = javaMailSender.createMimeMessage();
try {
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, mail);
message.setSubject("이메일 인증");
String body = String.format("""
<h3>요청하신 인증 번호입니다.</h3>
<h1>%s</h1>
<h3>감사합니다.</h3>
""", verificationCode);
message.setText(body, "UTF-8", "html");
} catch (MessagingException e) {
throw new BaseException("이메일 생성 중 오류가 발생했습니다: " + e.getMessage(),
HttpStatus.BAD_REQUEST);
}
return message;
}
/**
* 인증메일 발송 및 인증번호 반환
* @param mail 수신자 이메일 주소
* @return 생성된 인증번호
*/
public boolean sendMail(String mail) {
rateLimiter.checkRateLimit(mail);
String verificationCode = createVerificationCode();
verificationDataMap.put(mail, new VerificationData(verificationCode));
MimeMessage message = createMail(mail, verificationCode);
try{
javaMailSender.send(message);
return true;
}catch (Exception e) {
throw new BaseException("이메일 발송 중 오류가 발생했습니다: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
/**
* 인증번호 검증
* @param email 수신자 이메일 주소
* @param code 사용자가 입력한 인증번호
* @return 인증번호 일치 여부
*/
public boolean verifyCode(String email, String code) {
if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) {
return false;
}
VerificationData data = verificationDataMap.get(email);
if (data == null || data.isExpired()) {
return false;
}
boolean isVerified = data.code().equals(code);
if (isVerified){
verificationDataMap.remove(email);
}
return isVerified;
}
}