implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '3.0.5'
이건 블로그마다 조금씩 다른거 같다. 잘 돌아간다면 패스
spring:
mail:
host: smtp.gmail.com
port: 587
username: 본인 구글 이메일@gmail.com
password: 위에서 받은 16자리 번호 붙여서 넣기
properties:
mail:
smtp:
auth: true
starttls:
enable: true
required: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
auth-code-expiration-millis: 1800000
@Configuration
public class EmailProperties {
@Value("${spring.mail.host}")
private String host;
@Value("${spring.mail.port}")
private int port;
@Value("${spring.mail.username}")
private String username;
@Value("${spring.mail.password}")
private String password;
@Value("${spring.mail.properties.mail.smtp.auth}")
private boolean auth;
@Value("${spring.mail.properties.mail.smtp.starttls.enable}")
private boolean starttlsEnable;
@Value("${spring.mail.properties.mail.smtp.starttls.required}")
private boolean starttlsRequired;
@Value("${spring.mail.properties.mail.smtp.connectiontimeout}")
private int connectionTimeout;
@Value("${spring.mail.properties.mail.smtp.timeout}")
private int timeout;
@Value("${spring.mail.properties.mail.smtp.writetimeout}")
private int writeTimeout;
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(host);
mailSender.setPort(port);
mailSender.setUsername(username);
mailSender.setPassword(password);
mailSender.setDefaultEncoding("UTF-8");
mailSender.setJavaMailProperties(getMailProperties());
return mailSender;
}
private Properties getMailProperties() {
Properties properties = new Properties();
properties.put("mail.smtp.auth", auth);
properties.put("mail.smtp.starttls.enable", starttlsEnable);
properties.put("mail.smtp.starttls.required", starttlsRequired);
properties.put("mail.smtp.connectiontimeout", connectionTimeout);
properties.put("mail.smtp.timeout", timeout);
properties.put("mail.smtp.writetimeout", writeTimeout);
return properties;
}
}
@Slf4j
@Component
@Transactional
@RequiredArgsConstructor
public class EmailTool {
private final JavaMailSender javaMailSender;
public void sendEmail(String email, String title, String text) {
SimpleMailMessage emailForm = createEmailForm(email, title, text);
try {
javaMailSender.send(emailForm);
log.info("이메일 발송 성공");
} catch (Exception e) {
log.error("이메일 발송 오류");
}
}
// 발신할 이메일 데이터 세팅
private SimpleMailMessage createEmailForm(String toEmail, String title, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(toEmail);
message.setSubject(title);
message.setText(text);
return message;
}
}
@Slf4j
@RestController
@RequestMapping("/email-verification")
@RequiredArgsConstructor
public class EmailVerificationController {
private final EmailVerificationService emailVerificationService;
@PostMapping
public ResponseEntity<?> sendCode(
@Schema(description = "인증번호를 보낼 email 주소", example = "example@example.com")
@RequestParam("email")
String email) {
emailVerificationService.sendCodeToEmail(email);
return ResponseEntity.ok().body("발송완료");
}
@GetMapping
public ResponseEntity<?> verifyCode(
@Schema(description = "인증번호를 보낸 email 주소", example = "example@example.com")
@RequestParam("email")
String email,
@Schema(description = "받은 인증번호", example = "123456")
@RequestParam("code")
String code) {
return ResponseEntity.ok().body(emailVerificationService.verifyCode(email, code));
}
}
@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
public class EmailVerificationService {
private final UserRepository userRepository;
private final RedisTool redisTool;
private final EmailTool emailTool;
private static final String AUTH_CODE_PREFIX = "AuthCode ";
@Value("${spring.mail.auth-code-expiration-millis}")
private long authCodeExpirationMillis;
public void sendCodeToEmail(String toEmail) {
//이메일 중복 검사
if(userRepository.existsByEmail(toEmail)) throw new CustomException(ErrorCode.DUPLICATE_EMAIL);
//인증코드 생성, 저장 및 이메일 전송
String title = "유저 이메일 인증 번호";
String authCode = this.createCode();
// 이메일 인증 요청 시 인증 번호 Redis에 저장
redisTool.setValues(AUTH_CODE_PREFIX + toEmail,
authCode, Duration.ofMillis(authCodeExpirationMillis));
emailTool.sendEmail(toEmail, title, authCode);
}
public boolean verifyCode(String email, String authCode) {
String redisAuthCode = redisTool.getValues(AUTH_CODE_PREFIX + email);
return redisTool.checkExistsValue(redisAuthCode) && redisAuthCode.equals(authCode);
}
private String createCode() {
try {
Random random = SecureRandom.getInstanceStrong();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 6; i++) {
builder.append(random.nextInt(10));
}
return builder.toString();
} catch (NoSuchAlgorithmException e) {
throw new CustomException(ErrorCode.SERVER_ERROR);
}
}
}
데이터 베이스는 데이터를 물리 디스크에 직접 쓰기 때문에 서버에 문제가 발생하여 다운되더라도 데이터가 손실되지 않는다. 하지만 매번 디스크에 접근해야 하기 때문에 사용자가 많아질수록 부하가 많아져서 느려질 수 있다.
일반적으로 서비스 운영 초반이거나 규모가 작은, 사용자가 많지 않은 서비스의 경우에는 WEB - WAS - DB 의 구조로도 데이터 베이스에 무리가 가지 않는다.
하지만 사용자가 늘어난다면 데이터 베이스가 과부하 될 수 있기 때문에 이때 캐시 서버를 도입하여 사용한다.
그리고 이 캐시 서버로 이용할 수 있는 것이 바로 Redis 이다.
KEY : 인증코드
VALUE : 이메일 난수
//REDIS
implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.2.0'
data: redis: port: 6379 host: localhost
@Slf4j
@Component
@Transactional
@RequiredArgsConstructor
public class RedisTool {
private final RedisTemplate<String, Object> redisTemplate;
public void setValues(String key, String data) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(key, data);
}
public void setValues(String key, String data, Duration duration) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(key, data, duration);
}
@Transactional(readOnly = true)
public String getValues(String key) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
if (values.get(key) == null) {
return "false";
}
return (String) values.get(key);
}
public boolean checkExistsValue(String value) {
return !value.equals("false");
}
}
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {
private final RedisProperties redisProperties;
// RedisProperties로 yaml에 저장한 host, post를 연결
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort());
}
// serializer 설정으로 redis-cli를 통해 직접 데이터를 조회할 수 있도록 설정
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
.
.
참고 자료
📝 https://green-bin.tistory.com/83
.
.