Skip to content
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

#97 회원가입시 이메일 인증 기능 #98

Merged
merged 9 commits into from
May 10, 2024
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-websocket'

// redis
// implementation 'org.springframework.boot:spring-boot-starter-data-redis'

//security
implementation 'org.springframework.boot:spring-boot-starter-security'

// json
implementation 'org.json:json:20231013'

// email
implementation 'org.springframework.boot:spring-boot-starter-mail'

//lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand All @@ -51,6 +51,9 @@ dependencies {
// database
runtimeOnly 'com.mysql:mysql-connector-j'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

// mongo db
implementation 'org.springframework.boot:spring-boot-starter-data-mongodb'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.seoultech.synergybe.domain.common;

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import java.util.random.RandomGenerator;

@Slf4j
@Component
public class RandomNumber {

public String generateRandomNumber() {
// L128X256MixRandom
RandomGenerator generator = RandomGenerator.of("L128X256MixRandom");

String s1 = String.valueOf(generator.nextInt(10));
String s2 = String.valueOf(generator.nextInt(10));
String s3 = String.valueOf(generator.nextInt(10));
String s4 = String.valueOf(generator.nextInt(10));
String s5 = String.valueOf(generator.nextInt(10));
String s6 = String.valueOf(generator.nextInt(10));

log.info("random : " + s1 + s2 + s3 + s4 + s5 + s6);

return s1 + s2 + s3 + s4 + s5 + s6;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.seoultech.synergybe.domain.email;

import com.seoultech.synergybe.domain.common.RandomNumber;
import com.seoultech.synergybe.system.utils.RedisUtil;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class MailService {
private final JavaMailSender javaMailSender;
private final RandomNumber randomNumber;
private final RedisUtil redisUtil;

private int generateRandomNumber() {
// generate random number
String generatedNumber = randomNumber.generateRandomNumber();

return Integer.parseInt(generatedNumber);
}

public void validateEmail(String email) {
Integer authNumber = generateRandomNumber();
String from = "[email protected]";
String to = email;
String title = "[Synergy] 인증 이메일입니다.";
String htmlBody = "<div style='font-family: Arial, sans-serif;'>" +
"<p>안녕하세요, " + email + " 고객님</p>" +
"<br><br>" +
"<p>[Synergy] 를 방문해주셔서 감사합니다.</p>" +
"<br>" +
"<p>아래 발급된 이메일 인증번호를 복사하거나 직접 입력하여 인증을 완료해주세요.</p>" +
"<br>" +
"<p>개인정보 보호를 위해 인증번호는 5분 간 유효합니다.</p>" +
"<br><br>" +
"<p style='font-size: 20px; font-weight: bold; color: #0077ff;'>" + authNumber + "</p>" +
"<br><br>" +
"<p>인증번호를 입력해주시면 회원가입이 완료됩니다.</p>" +
"</div>";
sendMail(from, to, title, htmlBody, authNumber);
}

private void sendMail(String from, String to, String title, String content, Integer authNumber) {
MimeMessage message = javaMailSender.createMimeMessage();//JavaMailSender 객체를 사용하여 MimeMessage 객체를 생성
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");//이메일 메시지와 관련된 설정을 수행합니다.
// true를 전달하여 multipart 형식의 메시지를 지원하고, "utf-8"을 전달하여 문자 인코딩을 설정
helper.setFrom(from);//이메일의 발신자 주소 설정
helper.setTo(to);//이메일의 수신자 주소 설정
helper.setSubject(title);//이메일의 제목을 설정
helper.setText(content,true);//이메일의 내용 설정 두 번째 매개 변수에 true를 설정하여 html 설정으로한다.
javaMailSender.send(message);
} catch (MessagingException e) {//이메일 서버에 연결할 수 없거나, 잘못된 이메일 주소를 사용하거나, 인증 오류가 발생하는 등 오류
// 이러한 경우 MessagingException이 발생
e.printStackTrace();//e.printStackTrace()는 예외를 기본 오류 스트림에 출력하는 메서드
}
log.info("before setDataExpire");
redisUtil.setDataExpire(String.valueOf(authNumber), to, 60*5L);
}

public boolean checkAuthNumber(String email, String authNumber) {
if(redisUtil.getData(authNumber)==null){
return false;
}
else if(redisUtil.getData(authNumber).equals(email)){
return true;
}
else{
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.seoultech.synergybe.domain.user.service.UserService;
import com.seoultech.synergybe.domain.user.User;
import com.seoultech.synergybe.system.config.login.LoginUser;
import com.seoultech.synergybe.system.utils.EmailRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
Expand All @@ -28,14 +29,20 @@
public class UserController {

private final UserService userService;

@Operation(summary = "나의 정보", description = "내 프로필 정보가 반환됩니다.")
@GetMapping(value = "/me/info")
public ResponseEntity<GetUserAccountResponse> getMyInfo(@LoginUser String userId) {

return ResponseEntity.status(HttpStatus.OK).body(userService.getUserInfo(userId));
}

@PostMapping("/email-auth")
public ResponseEntity<Void> validateEmail(@Valid @RequestBody EmailRequest request) {
userService.validateEmail(request);

return ResponseEntity.noContent().build();
}

@Operation(summary = "유저 조회", description = "유저Id 기준으로 해당 유저를 반환합니다.")
@GetMapping(value = "/{userId}")
public ResponseEntity<GetUserAccountResponse> getUser(@PathVariable("userId") String userId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import com.seoultech.synergybe.domain.common.idgenerator.IdGenerator;
import com.seoultech.synergybe.domain.common.idgenerator.IdPrefix;
import com.seoultech.synergybe.domain.common.paging.ListResponse;
import com.seoultech.synergybe.domain.email.MailService;
import com.seoultech.synergybe.domain.user.dto.response.*;
import com.seoultech.synergybe.domain.user.exception.UserBadRequestException;
import com.seoultech.synergybe.domain.user.exception.UserNotFoundException;
import com.seoultech.synergybe.domain.user.repository.UserRepository;
import com.seoultech.synergybe.domain.user.User;
import com.seoultech.synergybe.domain.user.vo.UserEmail;
import com.seoultech.synergybe.system.exception.ErrorCode;
import com.seoultech.synergybe.system.utils.EmailRequest;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Predicate;
Expand All @@ -27,7 +29,6 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -39,6 +40,7 @@ public class UserService {
private final UserRepository userRepository;
private final CustomPasswordEncoder passwordEncoder;
private final IdGenerator idGenerator;
private final MailService mailService;

@Transactional
public String createUser(
Expand All @@ -60,6 +62,8 @@ public String createUser(
.build();
userRepository.save(user);

mailService.validateEmail(email);

return user.getId();
}

Expand Down Expand Up @@ -182,5 +186,14 @@ private List<String> extractIds(String response) {
throw new RuntimeException(e);
}
}

public void validateEmail(EmailRequest request) {
boolean isAuthorize = mailService.checkAuthNumber(request.email(), request.authNumber());

if (!isAuthorize) {
// todo
// 인증번호가 다르다면 회원가입 진행하지 않음
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.seoultech.synergybe.system.config;

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 {
@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.gmail.com");
mailSender.setPort(587);
mailSender.setUsername("[email protected]");
mailSender.setPassword("pgjizlzbapasfchs");

Properties javaMailProperties = new Properties();//JavaMail의 속성을 설정하기 위해 Properties 객체를 생성
javaMailProperties.put("mail.transport.protocol", "smtp");//프로토콜로 smtp 사용
javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요
javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용
javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화
javaMailProperties.put("mail.debug", "true");//디버깅 정보 출력
javaMailProperties.put("mail.smtp.ssl.trust", "smtp.google.com");//smtp 서버의 ssl 인증서를 신뢰
javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼

mailSender.setJavaMailProperties(javaMailProperties); //mailSender에 우리가 만든 properties 넣고

return mailSender;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.seoultech.synergybe.system.utils;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;

@Valid
public record EmailRequest(
@NotEmpty(message = "이메일을 입력해주세요.")
@Email
String email,

@NotEmpty(message = "인증 번호를 입력해주세요.")
String authNumber
) {

}
39 changes: 39 additions & 0 deletions src/main/java/com/seoultech/synergybe/system/utils/RedisUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.seoultech.synergybe.system.utils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.time.Duration;

@Slf4j
@Service
@RequiredArgsConstructor
public class RedisUtil {
private final StringRedisTemplate redisTemplate;

public String getData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 가져오는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
return valueOperations.get(key);
}

public void setData(String key,String value){//지정된 키(key)에 값을 저장하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
valueOperations.set(key,value);
}

public void setDataExpire(String key,String value,long duration){//지정된 키(key)에 값 을 저장하고, 지정된 시간(duration) 후에 데이터가 만료되도록 설정하는 메서드
ValueOperations<String,String> valueOperations=redisTemplate.opsForValue();
Duration expireDuration=Duration.ofSeconds(duration);
valueOperations.set(key,value,expireDuration);
log.info("key : " + key);
log.info("value : " + value);
log.info("duration : " + duration);
}

public void deleteData(String key){//지정된 키(key)에 해당하는 데이터를 Redis에서 삭제하는 메서드
redisTemplate.delete(key);
}
}
Loading