diff --git a/build.gradle b/build.gradle index 69f9d7a..e587790 100644 --- a/build.gradle +++ b/build.gradle @@ -45,6 +45,12 @@ dependencies { implementation 'com.google.oauth-client:google-oauth-client-jetty:1.23.0' implementation 'com.google.apis:google-api-services-youtube:v3-rev20230816-2.0.0' implementation 'com.google.http-client:google-http-client-jackson2:1.39.2' + + //mail 인증 + implementation 'org.springframework.boot:spring-boot-starter-mail' + + //redis 추가 + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } tasks.named('test') { diff --git a/src/main/java/com/example/beginnerfitbe/email/EmailDto.java b/src/main/java/com/example/beginnerfitbe/email/EmailDto.java new file mode 100644 index 0000000..6d7129b --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/email/EmailDto.java @@ -0,0 +1,12 @@ +package com.example.beginnerfitbe.email; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class EmailDto { + private String email; + private String code; +} + diff --git a/src/main/java/com/example/beginnerfitbe/email/config/EmailConfig.java b/src/main/java/com/example/beginnerfitbe/email/config/EmailConfig.java new file mode 100644 index 0000000..1e5680b --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/email/config/EmailConfig.java @@ -0,0 +1,68 @@ +package com.example.beginnerfitbe.email.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class EmailConfig { + + @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; + } +} \ No newline at end of file diff --git a/src/main/java/com/example/beginnerfitbe/email/controller/EmailController.java b/src/main/java/com/example/beginnerfitbe/email/controller/EmailController.java new file mode 100644 index 0000000..5d85a50 --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/email/controller/EmailController.java @@ -0,0 +1,28 @@ +package com.example.beginnerfitbe.email.controller; + +import com.example.beginnerfitbe.email.EmailDto; +import com.example.beginnerfitbe.email.service.EmailService; +import com.example.beginnerfitbe.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/auth") +public class EmailController { + private final EmailService emailService; + private final UserService userService; + @PostMapping("/email-send") + public ResponseEntity emailSend(@RequestParam String email) throws Exception { + if(userService.emailCheck(email)){ + return ResponseEntity.badRequest().body("이미 존재하는 회원입니다"); + } + return ResponseEntity.ok(emailService.sendEmail(email)); + } + @PostMapping("/email-verify") + public ResponseEntity codeVerification(@RequestBody EmailDto emailDto){ + return ResponseEntity.ok(emailService.codeVerification(emailDto)); + } + +} diff --git a/src/main/java/com/example/beginnerfitbe/email/service/EmailService.java b/src/main/java/com/example/beginnerfitbe/email/service/EmailService.java new file mode 100644 index 0000000..9f0e49c --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/email/service/EmailService.java @@ -0,0 +1,102 @@ +package com.example.beginnerfitbe.email.service; + +import java.util.Random; + +import com.example.beginnerfitbe.email.EmailDto; +import com.example.beginnerfitbe.error.StateResponse; +import com.example.beginnerfitbe.redis.service.RedisService; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class EmailService { + + private final JavaMailSender emailSender; + private final RedisService redisService; + + @Value("${spring.mail.username}") + private String fromEmail; + + private static final String ePw = createKey(); + + private MimeMessage createMessage(String to) throws Exception { + System.out.println("보내는 대상 : " + to); + System.out.println("인증 번호 : " + ePw); + MimeMessage message = emailSender.createMimeMessage(); + + message.addRecipients(MimeMessage.RecipientType.TO, to); // 보내는 대상 + message.setSubject("beginner fit sign-up"); // 제목 + + String msgg = ""; + msgg += "
"; + msgg += "

!BEGINNERFIT!

"; + msgg += "
"; + msgg += "
"; + msgg += "
"; + msgg += "

회원가입 인증 코드입니다.

"; + msgg += "
"; + msgg += "CODE : "; + msgg += ePw + "

"; + msgg += "
"; + message.setText(msgg, "utf-8", "html"); // 내용 + message.setFrom(new InternetAddress(fromEmail, "beginnerfit")); // 보내는 사람 + + return message; + } + + public static String createKey() { + StringBuffer key = new StringBuffer(); + Random rnd = new Random(); + + for (int i = 0; i < 8; i++) { // 인증코드 8자리 + int index = rnd.nextInt(3); // 0~2 까지 랜덤 + + switch (index) { + case 0: + key.append((char) ((int) (rnd.nextInt(26)) + 97)); + break; + case 1: + key.append((char) ((int) (rnd.nextInt(26)) + 65)); + break; + case 2: + key.append((rnd.nextInt(10))); + break; + } + } + return key.toString(); + } + + public String sendEmail(String to) throws Exception { + MimeMessage message = createMessage(to); + try { + emailSender.send(message); + redisService.setDataExpire(ePw, to, 60 * 5L); + } catch (MailException es) { + es.printStackTrace(); + throw new IllegalArgumentException(); + } + return ePw; + } + + public StateResponse codeVerification(EmailDto emailDto) { + String email = emailDto.getEmail(); + String code = emailDto.getCode(); + + if (redisService.getData(code) != null && redisService.getData(code).equals(email)) { + return StateResponse.builder() + .code("SUCCESS") + .message("이메일 인증에 성공했습니다.") + .build(); + } + return StateResponse.builder() + .code("FAIL") + .message("이메일 인증에 실패했습니다.") + .build(); + } +} diff --git a/src/main/java/com/example/beginnerfitbe/jwt/config/SecurityConfig.java b/src/main/java/com/example/beginnerfitbe/jwt/config/SecurityConfig.java index 4764040..089bb4a 100644 --- a/src/main/java/com/example/beginnerfitbe/jwt/config/SecurityConfig.java +++ b/src/main/java/com/example/beginnerfitbe/jwt/config/SecurityConfig.java @@ -26,6 +26,8 @@ public class SecurityConfig { private static final String[] publicEndpoints = { "/auth/sign-in", "/auth/sign-up", + "auth/email-send", + "/auth/email-verify", "/users/emailcheck", "/users/health-info", "/swagger-ui.html", diff --git a/src/main/java/com/example/beginnerfitbe/redis/config/RedisConfig.java b/src/main/java/com/example/beginnerfitbe/redis/config/RedisConfig.java new file mode 100644 index 0000000..875faea --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/redis/config/RedisConfig.java @@ -0,0 +1,30 @@ +package com.example.beginnerfitbe.redis.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisHost, redisPort); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(redisConnectionFactory()); + return template; + } +} diff --git a/src/main/java/com/example/beginnerfitbe/redis/service/RedisService.java b/src/main/java/com/example/beginnerfitbe/redis/service/RedisService.java new file mode 100644 index 0000000..b457ef6 --- /dev/null +++ b/src/main/java/com/example/beginnerfitbe/redis/service/RedisService.java @@ -0,0 +1,31 @@ +package com.example.beginnerfitbe.redis.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.time.Duration; + +@Service +@RequiredArgsConstructor +public class RedisService { + private final StringRedisTemplate redisTemplate; + + public String getData(String key){ + ValueOperations valueOperations=redisTemplate.opsForValue(); + return valueOperations.get(key); + } + public void setData(String key,String value){ + ValueOperations valueOperations=redisTemplate.opsForValue(); + valueOperations.set(key,value); + } + public void setDataExpire(String key,String value,long duration){ + ValueOperations valueOperations=redisTemplate.opsForValue(); + Duration expireDuration= Duration.ofSeconds(duration); + valueOperations.set(key,value,expireDuration); + } + public void deleteData(String key){ + redisTemplate.delete(key); + } +} diff --git a/src/main/java/com/example/beginnerfitbe/user/controller/UserController.java b/src/main/java/com/example/beginnerfitbe/user/controller/UserController.java index 2582644..6952522 100644 --- a/src/main/java/com/example/beginnerfitbe/user/controller/UserController.java +++ b/src/main/java/com/example/beginnerfitbe/user/controller/UserController.java @@ -67,11 +67,4 @@ public ResponseEntity withdrawal(HttpServletRequest request) { return userService.withdrawal(userId); } - @GetMapping("/emailcheck") - @Operation(summary="이메일 중복 확인 메서드", description = "이메일을 받아 존재하는지 확인하는 메서드입니다.") - ResponseEntity emailCheck(@RequestParam(value = "email") String email){ - return ResponseEntity.ok(userService.emailCheck(email)); - } - - } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 82bcf1e..52a5d12 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,6 +14,28 @@ spring: username: ${DATABASE_USER} password: ${DATABASE_PASSWORD} + mail: + host: smtp.gmail.com + port: 587 + username: ${EMAIL} + password: ${PASSWORD} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + required: true + connectiontimeout: 5000 + timeout: 5000 + writetimeout: 5000 + auth-code-expiration-millis: 1800000 + + data: + redis: + host: localhost + port: 6379 + jwt: secret: ${JWT_SECRET} expiration: ${JWT_EXPIRATION}