-
Notifications
You must be signed in to change notification settings - Fork 0
๐ Redis Email Send #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7496060
74386e1
72969d8
ecc236b
4391b23
d165832
fa72e44
81dbc82
f307fc3
5e12d71
4121cf6
c3ea46e
f7ec6d2
371a66b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.mycom.socket.auth.config; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @Component | ||
| @ConfigurationProperties(prefix = "spring.mail") | ||
| public class MailProperties { | ||
| private String host; | ||
| private int port; | ||
| private String protocol; | ||
| private String username; | ||
| private String password; | ||
| private String senderEmail; | ||
| private String senderName; | ||
| private String subject; | ||
| private String bodyTemplate; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,37 @@ | ||
| package com.mycom.socket.auth.service; | ||
|
|
||
| import com.mycom.socket.auth.config.MailProperties; | ||
| import com.mycom.socket.auth.dto.response.EmailVerificationResponse; | ||
| import com.mycom.socket.auth.service.data.VerificationData; | ||
| import com.mycom.socket.global.exception.BaseException; | ||
| import com.mycom.socket.global.service.RedisService; | ||
| 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; | ||
| private final RedisService redisService; | ||
| private final MailProperties mailProperties; | ||
|
|
||
| /** | ||
| * 6์๋ฆฌ ๋์ ์ธ์ฆ๋ฒํธ ์์ฑ | ||
| * SecureRandom ์ฌ์ฉํ์ฌ ๋ณด์์ฑ ํฅ์ | ||
| * @return 100000~999999 ๋ฒ์์ ์ธ์ฆ๋ฒํธ | ||
| * 6์๋ฆฌ ์ธ์ฆ๋ฒํธ ์์ฑ (100000-999999) | ||
| */ | ||
| private String createVerificationCode() { | ||
| // Math.random()์ ์์ธก ๊ฐ๋ฅํ ๋์๋ฅผ ์์ฑํ ์ ์์ด ๋ณด์์ ์ทจ์ฝ | ||
| // SecureRandom์ ์ํธํ์ ์ผ๋ก ์์ ํ ๋์๋ฅผ ์์ฑํ๋ฏ๋ก ์ธ์ฆ๋ฒํธ ์์ฑ์ ๋ ์ ํฉ | ||
| SecureRandom secureRandom = new SecureRandom(); | ||
| return String.format("%06d", secureRandom.nextInt(1000000)); | ||
| return String.format("%06d", new SecureRandom().nextInt(1000000)); | ||
| } | ||
|
|
||
| public boolean isEmailVerified(String email) { | ||
| return redisService.isEmailVerified(email); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -48,14 +42,10 @@ private String createVerificationCode() { | |
| public MimeMessage createMail(String email, String verificationCode) { | ||
| MimeMessage message = javaMailSender.createMimeMessage(); | ||
| try { | ||
| message.setFrom(senderEmail); | ||
| message.setFrom(mailProperties.getSenderEmail()); | ||
| message.setRecipients(MimeMessage.RecipientType.TO, email); | ||
| message.setSubject("์ด๋ฉ์ผ ์ธ์ฆ"); | ||
| String body = String.format(""" | ||
| <h3>์์ฒญํ์ ์ธ์ฆ ๋ฒํธ์ ๋๋ค.</h3> | ||
| <h1>%s</h1> | ||
| <h3>๊ฐ์ฌํฉ๋๋ค.</h3> | ||
| """, verificationCode); | ||
| String body = String.format(mailProperties.getBodyTemplate(), verificationCode); | ||
| message.setText(body, "UTF-8", "html"); | ||
| } catch (MessagingException e) { | ||
| throw new BaseException("์ด๋ฉ์ผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), | ||
|
|
@@ -70,15 +60,18 @@ public MimeMessage createMail(String email, String verificationCode) { | |
| * @return ์์ฑ๋ ์ธ์ฆ๋ฒํธ | ||
| */ | ||
| public EmailVerificationResponse sendMail(String email) { | ||
| rateLimiter.checkRateLimit(email); | ||
| if (redisService.incrementCount(email) > 3) { | ||
| throw new BaseException("๋๋ฌด ๋ง์ ์์ฒญ์ ๋๋ค. 1๋ถ ํ์ ๋ค์ ์๋ํด์ฃผ์ธ์.", | ||
| HttpStatus.TOO_MANY_REQUESTS); | ||
| } | ||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์์ฒญ ํ์ ์ ํ์ ๋ํ ๋ง๋ฃ ์๊ฐ ์ค์ ํ์
|
||
|
|
||
| String verificationCode = createVerificationCode(); | ||
| verificationDataMap.put(email, new VerificationData(verificationCode)); | ||
| redisService.saveCode(email, verificationCode); | ||
|
|
||
| MimeMessage message = createMail(email, verificationCode); | ||
| try { | ||
| javaMailSender.send(message); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ ์ก ์ฑ๊ณต"); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ ์ก ์ฑ๊ณต"); // ๋ฉ์์ง ์์ | ||
| } catch (Exception e) { | ||
| throw new BaseException("์ด๋ฉ์ผ ๋ฐ์ก ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), | ||
| HttpStatus.INTERNAL_SERVER_ERROR); | ||
|
|
@@ -93,30 +86,19 @@ public EmailVerificationResponse sendMail(String email) { | |
| * @return ์ธ์ฆ๋ฒํธ ์ผ์น ์ฌ๋ถ | ||
| */ | ||
| public EmailVerificationResponse verifyCode(String email, String code) { | ||
| validateVerificationCode(code); | ||
|
|
||
| VerificationData data = verificationDataMap.get(email); | ||
| if (data == null || data.isExpired()) { | ||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ๋ง๋ฃ๋์๊ฑฐ๋ ์กด์ฌํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| if (!code.matches("\\d{6}")) { | ||
| throw new BaseException("์ ํจํ์ง ์์ ์ธ์ฆ ์ฝ๋ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| if (!data.code().equals(code)) { | ||
| try { | ||
| String saveCode = redisService.getCode(code); // ์ธ์ฆ์ฝ๋ ๊ฒ์ฆ | ||
| if(!saveCode.equals(code)) { | ||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค."); | ||
| } catch (Exception e) { | ||
| throw new BaseException("์ธ์ฆ ์ฝ๋๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| verificationDataMap.put(email, data.withVerified()); | ||
| return EmailVerificationResponse.of("์ด๋ฉ์ผ ์ธ์ฆ์ด ์๋ฃ๋์์ต๋๋ค."); | ||
| } | ||
|
|
||
| private void validateVerificationCode(String code) { | ||
| if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) { | ||
| throw new BaseException("์ ํจํ์ง ์์ ์ธ์ฆ ์ฝ๋ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||
| } | ||
| } | ||
|
|
||
| public boolean isEmailVerified(String email) { | ||
| VerificationData data = verificationDataMap.get(email); | ||
| return data != null && !data.isExpired() && data.verified(); | ||
| } | ||
|
|
||
| } | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package com.mycom.socket.global.config; | ||
|
|
||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.data.redis.connection.RedisStandaloneConfiguration; | ||
| import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; | ||
| import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | ||
| import org.springframework.data.redis.core.RedisTemplate; | ||
| import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; | ||
| import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
|
||
| import java.time.Duration; | ||
|
|
||
| @Configuration | ||
| @RequiredArgsConstructor | ||
| @EnableRedisRepositories | ||
| public class RedisConfig { | ||
| private final RedisProperties redisProperties; | ||
|
|
||
| @Bean | ||
| public LettuceConnectionFactory redisConnectionFactory() { | ||
| LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder() | ||
| .commandTimeout(Duration.ofSeconds(1)) | ||
| .build(); | ||
| RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration( | ||
| redisProperties.getHost(), | ||
| redisProperties.getPort() | ||
| ); | ||
| return new LettuceConnectionFactory(serverConfig, clientConfig); | ||
| } | ||
|
|
||
| @Bean | ||
| public RedisTemplate<?, ?> redisTemplate() { | ||
| RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); | ||
| redisTemplate.setConnectionFactory(redisConnectionFactory()); | ||
|
|
||
| redisTemplate.setKeySerializer(new StringRedisSerializer()); | ||
| redisTemplate.setValueSerializer(new StringRedisSerializer()); | ||
| return redisTemplate; | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.mycom.socket.global.config; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.Setter; | ||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Getter | ||
| @Setter | ||
| @Component | ||
| @ConfigurationProperties(prefix = "spring.data.redis") | ||
| public class RedisProperties { | ||
|
|
||
| private String host; | ||
| private int port; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
๐ก Codebase verification
API ๋ฌธ์ํ๊ฐ ํ์ํฉ๋๋ค
์๋ํฌ์ธํธ ๋ณ๊ฒฝ์ฌํญ(
/verify-email)์ด ๋ฌธ์ํ๋์ด ์์ง ์์ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ๋ค์ ์ฌํญ๋ค์ ์ถ๊ฐํด์ฃผ์๊ธฐ ๋ฐ๋๋๋ค:๐ Analysis chain
์๋ํฌ์ธํธ ์ด๋ฆ์ด ๊ฐ์ ๋์์ต๋๋ค!
์๋ก์ด ์๋ํฌ์ธํธ ์ด๋ฆ
/verify-email์ด ๊ธฐ๋ฅ์ ๋ ๋ช ํํ๊ฒ ์ค๋ช ํฉ๋๋ค.API ๋ฌธ์๊ฐ ์ ๋ฐ์ดํธ๋์๋์ง ํ์ธํ๊ธฐ ์ํด ๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ธ์:
๐ Scripts executed
The following scripts were executed for the analysis:
Script:
Length of output: 63
Script:
Length of output: 407