비밀번호 재설정을 위한 이메일 인증
JWT를 이용하여 로그인을 구현하였고, 그 다음으로 회원이 비밀번호를 잃어버린 상황을 고려해야 했다.
회원이 비밀번호를 잃어버린 상황이라면, 어떻게 사용자를 인증하고, 비밀번호 재설정 권한을 줄지 로직을 생각해야 했다.
그래서 이메일 인증 방식으로 인증 코드를 보내를 것으로 로직을 지정하였다.
EmailConfig
Java에서 제공하는 JavaMailSenderImpl을 이용하여 사용자에게 email을 보낼 수 있게 하는 Config를 정의한다.
JavaMailSenderImpl을 EmailService에서 사용할 것이다.
@Configuration
@PropertySource("classpath:email.properties")
public class EmailConfig {
@Value("${mail.smtp.port}")
private int port;
@Value("${mail.smtp.socketFactory.port}")
private int socketPort;
@Value("${mail.smtp.auth}")
private boolean auth;
@Value("${mail.smtp.starttls.enable}")
private boolean starttls;
@Value("${mail.smtp.starttls.required}")
private boolean startlls_required;
@Value("${mail.smtp.socketFactory.fallback}")
private boolean fallback;
@Value("${AdminMail.id}")
private String id;
@Value("${AdminMail.password}")
private String password;
@Bean
public JavaMailSender javaMailService(){
JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
javaMailSender.setHost("smtp.gmail.com");
javaMailSender.setUsername(id);
javaMailSender.setPassword(password);
javaMailSender.setPort(port);
javaMailSender.setJavaMailProperties(getMailProperties());
javaMailSender.setDefaultEncoding("UTF-8");
return javaMailSender;
}
private Properties getMailProperties(){
Properties pt = new Properties();
pt.put("mail.smtp.socketFactory.port", socketPort);
pt.put("mail.smtp.auth", auth);
pt.put("mail.smtp.starttls.enable", starttls);
pt.put("mail.smtp.starttls.required", startlls_required);
pt.put("mail.smtp.socketFactory.fallback",fallback);
pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
return pt;
}
}
EmailService interface
인터페이스로 이메일 서비스를 정의하여 다형성을 위해 인터페이스로 구현하였다.
public interface EmailService {
String sendSimpleMessage(String to)throws Exception;
}
EmailServiceImpl
emailService를 implements하여서 이메일 서비스를 구현하였다.
인증 코드로 보낼 값을 무작위 문자열로 만들어 내는 generateRandomString을 사용하여 진행했다.
그 다음으로 무작위 문자열을 메일로 보내기 위해 html파일을 만드는 genrateMessage를 구현했다.
여기서는 Java에서 제공하는 JavaMailSender를 사용했다.
그리고 이 메일을 보내는 sendSimpleMessage를 이용해서 실제 메일을 사용자에게 보내준다. 그리고 보낸 메일을 서버 내부에서 확인 할 수 있게 인증 번호(무작위 문자열)을 반환 해준다.
@Slf4j
@Service
public class EmailServiceImpl implements EmailService{
@Autowired
JavaMailSender emailSender;
public static final String ePw = generateRandomString(10);
private MimeMessage generateMessage(String to)throws Exception{
log.info("보내는 대상 :"+ to);
log.info("인증 번호 :"+ePw);
MimeMessage message = emailSender.createMimeMessage();
message.addRecipients(Message.RecipientType.TO, to);
message.setSubject("이메일 인증");
String text="";
text +="<div style='margin:20px;'>" +
"<h1> 뛰러 왔는데 비밀 번호를 잊었슈? <h1>" +
"<br>" +
"<p>아래 코드를 복사해 입력 해주세요<p>" +
"<br>" +
"<div align='center' style='border:1px solid black: font-family:verdana';>" +
"<h3 style='color:blue;'>비밀번호 재설정 인증 코드입니다.</h3>" +
"<div style='font-size:130%'>" +
"CODE : <strong>" +
ePw+"<strong><div><br/> " +
"</div>";
message.setText(text,"utf-8","html");
message.setFrom(new InternetAddress("parkjkjk010@gamil.com","runningLion"));//보내는 사람
return message;
}
public static String generateRandomString(int length) {
// 무작위 문자열을 생성할 문자열 세트
String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// 문자열 생성기를 위한 랜덤 객체 생성
Random random = new Random();
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++) {
// 문자열 세트에서 무작위 문자 선택
int randomIndex = random.nextInt(characters.length());
char randomChar = characters.charAt(randomIndex);
// 선택한 문자를 문자열에 추가
sb.append(randomChar);
}
return sb.toString();
}
@Override
public String sendSimpleMessage(String to) throws Exception {
MimeMessage message = generateMessage(to);
try {
emailSender.send(message);
}catch (MailException ex){
log.info(ex.getMessage());
throw new IllegalArgumentException();
}
return ePw;
}
}
MemberEditController
멤버의 데이터베이스 정보를 수정하는 작업을 수행하는 부분을 정의한다.
아이디를 조회하는 경우 handleFindMemberName을 통해서 가져온 멤버의 이름과 전화번호로 멤버의 아이디를 찾아서 반환해준다.
sendAuthMail 메서드를 통해서 이메일을 사용자에게 보내고, 이메일을 확인한 사용자가 인증코드를 입력하면 checkAuthCode메서드를 통해서 사용자 데이터베이스에 저장한 authCode와의 일치 여부를 판별하여 비밀번호 재설정 권한을 준다.
@Slf4j
@RestController
@CrossOrigin(origins = "*", allowedHeaders = "*")
public class MemberEditController {
private final MemberService memberService;
private final EmailService emailService;
@Autowired
public MemberEditController(MemberService memberService, EmailService emailService) {
this.memberService = memberService;
this.emailService = emailService;
}
@PostMapping("/findUsername")
public String handleFindMemberName(@RequestBody MemberDto memberDto) {
// 아이디 찾기 로직 수행 후 결과를 반환
Optional<Member> member = memberService.findByNameAndPhoneNum(memberDto);
log.info(String.valueOf(member.isEmpty()));
return member.map(Member::getMemberId).orElse(null);
}
@PostMapping("/auth-send")
public boolean sendAuthMail(@RequestBody MemberDto memberDto) throws Exception {
// 비밀번호 재설정 로직 수행 후 결과를 모델에 추가하고 결과 화면 템플릿을 반환...
Optional<Member> member = memberService.findByNameAndPhoneNum(memberDto);
if(member.isPresent()){
String memberId = member.map(Member::getMemberId).orElseThrow();
String phoneNum = member.map(Member::getPhoneNum).orElseThrow();
log.info(phoneNum);
log.info(memberId);
String message = emailService.sendSimpleMessage(memberId);
return memberService.authCodeEdit(member.get(),message);
}
return false;
}//이메일, 전화번호, 이름을 통해서 인증코드 보내기, AuthCode member에 저장
@PostMapping("/auth-check")
public boolean checkAuthCode(@RequestBody AuthDto authDto){
return memberService.checkAuthCode(authDto.getMemberId(),authDto.getAuthCode());
}
// 인증코드 인증 부분 구현해야함
}
MemberService
인증 번호를 확인하는 checkAuthCode와 새로 발급한 authoCode를 member 정보로 저장하는 authCodeEdit을 정의했다.
이 AuthCode를 수정하는 과정에서 build를 통해서 데이터를 갱신해줘야하기 때문에 Member 부분에도 builder를 만드는 메서드를 구현해야했다.
@Slf4j
@Service
public class MemberService {
...
@Transactional
public boolean checkAuthCode(String memberId, String authCode){
log.info("MemberId = {}",memberId);
log.info("authCode = {}",authCode);
Optional<Member> member = memberJpaRepository.findMemberByMemberId(memberId);
log.info(String.valueOf(member.isEmpty()));
return member.map(value -> value.getAuthCode().equals(authCode)).orElse(false);
}
@Transactional
public boolean authCodeEdit(Member member, String message){
Optional<Member> value = memberJpaRepository.findMemberByMemberId(member.getMemberId());
if(value.isPresent()){
MemberEditDto build = value.get().toEditor().AuthCode(message).build();
value.get().edit(build);
log.info("인증 코드 갱신 성공");
return true;
}
log.info("인증 코드 갱신 실패");
return false;
}
Member
authCodeEdit에서 authCode수정을 위한 edit과 toEditor를 통해서 builder를 생성한다.
public void edit(MemberEditDto memberEditDto) {
this.authCode = memberEditDto.getAuthCode();
this.name = memberEditDto.getName();
this.password = memberEditDto.getPassword();
this.activated = memberEditDto.isActivated();
this.phoneNum = memberEditDto.getPhoneNum();
this.authorities = memberEditDto.getAuthorities();
}
public MemberEditDto.MemberEditDtoBuilder toEditor(){
return MemberEditDto.builder()
.memberId(memberId)
.name(name)
.activated(activated)
.password(password)
.phoneNum(phoneNum)
.AuthCode(authCode);
}
이를 통해서 이메일 인증을 구현하였다.
추가로 이 과정을 위해서는 구글에서 2차 인증을 하고, 보안에 들어가서 앱 비밀번호를 발급받아서 사용한다.
그렇게 email.properties를 정의한다.
mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465
AdminMail.id = 이메일
AdminMail.password = 앱 비밀번호
'LikeLion🦁' 카테고리의 다른 글
뛰슈 NGINX+Docker+Spring boot 설정 (0) | 2023.11.05 |
---|---|
뛰슈 서버 NGINX + SSL인증하기 (0) | 2023.11.01 |
또 떠나는 여행 이번엔 경주유🚋 (13) | 2023.09.01 |
뛰슈 - JWT (0) | 2023.08.31 |
뛰슈 - guest 모임 참여 (0) | 2023.08.22 |