비밀번호 기능 구현 전 메일 테스트까지 완료했다
/templates/member/login.html 비밀번호 초기화 버튼
<a href="/member/reset" class="btn btn-sm btn-info">비밀번호 초기화</a>
- <a> 태그를 통해 비밀번호 초기화시 이동될 url 연결
/controller/MemberController.java reset() 메서드 추가
@GetMapping("/reset")
public String reset() {
return "member/reset"; // /templates/member/reset.html
}
- http://localhost:8080/member/reset 접속시 reset.html 화면을 띄움
/templates/member/reset.html 생성 -> register.html 가져와서 수정
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="card container my-3 form-register">
<div class="my-3 border-bottom">
<h4>비밀번호 초기화</h4>
</div>
<form th:action="@{/mail/reset-mail}" method="post" >
<div class="mb-3">
<label for="email" class="form-lable">이메일</label>
<input type="email" id="email" name="email" class="form-control" required>
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-sm btn-primary mx-2">초기화</button>
<a href="/" class="btn btn-sm btn-secondary">취소</a>
</div>
</form>
</div>
</html>
💥💥💥
어제 postman에서 restful API 테스를 위해 CSRF를 꺼놨었다.
오늘 로그인을 하고 게시글을 등록하니 CSRF관련 오류가 발생했다.
알고보니 create.html과 modify.html에 적어두었던 CSRF토큰 null 오류였다.
따라서, /board/create.html, /reply/modify.html에 있는 CSRF 관련 태그 주석처리해야 함!
<!-- <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" /> -->
/controller/MailController.java 생성, /mail/reset-mail GET매핑 메서드 생성
- MailController.java를 만들기 전...
- Member엔티티에서 email을 가져오는 메서드 없기 때문에 이메일 주소를 가져올 수 있는 메서드를 추가해야한다.
- 🔽 /service/MemberService.java에 메일 주소로 검색하는 메서드 getMemberByEmail() 추가
// 24.06.28 이메일로 사용자 검색 메서드
public Member getMemberByEmail(String email){
Optional<Member> member = this.memberRepository.findByEmail(email);
if(member.isPresent())
return member.get();
else
throw new NotFoundException("Member not found!");
}
- /controller/MailController.java 생성, /mail/reset-mail GET매핑 메서드 생성
package com.eunji.backboard.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import com.eunji.backboard.entity.Member;
import com.eunji.backboard.service.MailService;
import com.eunji.backboard.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.PostMapping;
@RequestMapping("/mail")
@RequiredArgsConstructor
@Controller
@Log4j2
public class MailController {
private final MemberService memberService; // 회원가입한 아이디의 메일주소를 확인하기 위함
private final MailService mailService;
@PostMapping("/reset-mail")
public String reset_mail(Model model, @RequestParam("email") String email) {
log.info(String.format("▶▶▶ reset.html에서 넘어온 이메일: %s", email));
/*
* DB에서 메일주소가 있는지 확인
* 있으면 초기화 메일 보내고
* 없으면 에러
*/
try{
Member member = this.memberService.getMemberByEmail(email);
// 메일 전송
Boolean result = this.mailService.sendResetPasswordEmail(member.getEmail());
if(result) {
log.info("▶▶▶ 초기화 메일 전송 완료!!");
model.addAttribute("result", "초기화 메일 전송 성공!");
} else {
model.addAttribute("result", "초기화 메일 전송 실패! 관리자에게 문의하세요.");
}
} catch(Exception e) {
model.addAttribute("result", "초기화 메일 전송 실패! 사용자가 없습니다.");
}
return "member/reset_result"; // /templates/member/reset_result.html 파일
}
}
💥 💥 💥
- restcontroller에 만들었던 MailController.java와 이름 동일 문제로 에러 발생
- restcontroller/ MailController -> RestMailController.java로 이름 변경
/templates/member/reset_result.html 생성
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="card container my-3 form-register">
<div class="my-3 border-bottom">
<h4>비밀번호 초기화</h4>
</div>
<div class="mb-3">
<div th:text="${result}"></div>
</div>
<div class="d-flex justify-content-center">
<a href="/" class="btn btn-sm btn-secondary">Home</a>
</div>
</div>
</html>
/service/MailService.java에 메일전송 메서드 생성, 수정
// 메일에서 초기화할 화면으로 이동 URL
private String resetPassUrl = "http://localhost:8080/member/reset-password";
// 패스워드 초기화 메일 전송 METHOD
public Boolean sendResetPasswordEmail(String email) {
String subject = "요청하신 비밀번호 재설정입니다.";
String message = "BackBoard"
+ "<br><br>" + "아래 링크를 클릭하면 비밀번호 재설정 페이지로 이동합니다." + "<br>"
+ "<a href='" + resetPassUrl + "/" + email + "'>"
+ resetPassUrl + "/" + email + "</a>" + "<br><br>";
try{
sendMail(email, subject, message);
return true;
}catch(Exception e) {
return false;
}
}
- sendMail() 메서드 수정
mmh.setText(message, true);
- UUID를 생성해서 메일로 전송하는 메서드 추가
// 중복되지 않는 ID생성
private String makeUuid() {
return UUID.randomUUID().toString();
}
- sendResetPasswordEmail() 메서드 수정
public Boolean sendResetPasswordEmail(String email) {
String uuid = makeUuid();
String subject = "요청하신 비밀번호 재설정입니다.";
String message = "BackBoard"
+ "<br><br>" + "아래 링크를 클릭하면 비밀번호 재설정 페이지로 이동합니다." + "<br>"
+ "<a href='" + resetPassUrl + "/" + uuid + "'>"
+ resetPassUrl + "/" + uuid + "</a>" + "<br><br>";
try{
sendMail(email, subject, message);
return true;
}catch(Exception e) {
return false;
}
}
- resetPassUrl 뒤에 UUID가 붙는 것을 확인할 수 있음
/entity/Reset.java 생성
package com.eunji.backboard.entity;
import java.time.LocalDateTime;
import org.springframework.data.annotation.CreatedDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Reset {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String email;
private String uuid;
@CreatedDate
@Column(updatable = false)
private LocalDateTime regDate;
}
- h2-console에서 reset 테이블이 만들어진 것을 확인할 수 있음
/repository/ResetRepository.java 인터페이스 생성, findByUuid() 추가
package com.eunji.backboard.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import com.eunji.backboard.entity.Reset;
public interface ResetRepository extends JpaRepository<Reset, Integer>{
Optional<Reset> findByUuid(String uuid); // UUID 테이블 검색
}
/service/ResetService.java 생성
package com.eunji.backboard.service;
import java.time.LocalDateTime;
import java.util.Optional;
import org.springframework.stereotype.Service;
import com.eunji.backboard.common.NotFoundException;
import com.eunji.backboard.entity.Reset;
import com.eunji.backboard.repository.ResetRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
@RequiredArgsConstructor
@Service
@Log4j2
public class ResetService {
private final ResetRepository resetRepository;
public void setReset(String uuid, String email) {
Reset reset = Reset.builder().uuid(uuid).email(email).regDate(LocalDateTime.now()).build();
this.resetRepository.save(reset);
log.info("⏺⏺⏺ setReset() 성공!!!" );
}
public Reset getReset(String uuid) {
Optional<Reset> _reset = this.resetRepository.findByUuid(uuid);
if(_reset.isPresent()) {
log.info("⏺⏺⏺ getReset() 데이터있음!!!" );
return _reset.get();
} else {
throw new NotFoundException("Reset not found!");
}
}
}
/service/MailService.java에 ResetService 객체 생성, 메일 전송 후 setReset()을 사용하여 DB에 insert하기
private final ResetService resetService; // ResetService는 예외
// 패스워드 초기화 메일 전송 METHOD(!!!)
@Transactional
public Boolean sendResetPasswordEmail(String email) {
String uuid = makeUuid();
String subject = "요청하신 비밀번호 재설정입니다.";
String message = "BackBoard"
+ "<br><br>" + "아래 링크를 클릭하면 비밀번호 재설정 페이지로 이동합니다." + "<br>"
+ "<a href='" + resetPassUrl + "/" + uuid + "'>"
+ resetPassUrl + "/" + uuid + "</a>" + "<br><br>";
try{
sendMail(email, subject, message);
saveUuidAndEmail(uuid, email);
return true;
}catch(Exception e) {
return false;
}
}
// uuid 정보를 db에 넣기 위한 method
private void saveUuidAndEmail(String uuid, String email) {
this.resetService.setReset(uuid, email);
}
- 변경된 부분
- 메일전송후 생성된 UUID를 DB에 저장하기 위해 MailService에서 ResetService 객체를 생성
(일종의 예외처리) - ⭐ 트랜잭션 공부하기⭐
/controller/MemberController.java, /mail/reset-password GET매핑 메서드 작성
@GetMapping("/reset-password/{uuid}")
public String reset_password(MemberForm memberForm, @PathVariable("uuid") String uuid) {
Reset reset = this.resetService.getReset(uuid);
log.info(String.format("▶▶▶ 확인된 이메일: [%s]", reset.getEmail()));
// /reset-password/{uuid}로 들어갔을 때 사용자 이름과 이메일을 가져오기 위함
Member member = this.memberService.getMemberByEmail(reset.getEmail());
memberForm.setUsername(member.getUsername());
memberForm.setEmail(member.getEmail());
return "member/newpassword";
}
/templates/member/newpassword.html 생성
<!doctype html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org" layout:decorate="~{layout}">
<div layout:fragment="main-content" class="card container my-3 form-register">
<div class="my-3 border-bottom">
<h4>비밀번호 초기화</h4>
</div>
<form th:action="@{/member/reset-password}" th:object="${memberForm}" method="post" >
<!-- /templates/errors.html -->
<div th:replace="~{errors :: formErrorFragment}"></div>
<div class="mb-3">
<label for="username" class="form-lable">사용자이름</label>
<input type="text" th:field="*{username}" class="form-control bg-light" readonly>
</div>
<div class="mb-3">
<label for="email" class="form-lable">이메일</label>
<input type="email" th:field="*{email}" class="form-control bg-light" readonly>
</div>
<div class="mb-3">
<label for="password1" class="form-lable">비밀번호</label>
<input type="password" th:field="*{password1}" class="form-control">
</div>
<div class="mb-3">
<label for="password2" class="form-lable">비밀번호 확인</label>
<input type="password" th:field="*{password2}" class="form-control">
</div>
<div class="d-flex justify-content-center">
<button type="submit" class="btn btn-sm btn-primary mx-2">비밀번호 초기화</button>
<a href="/" class="btn btn-sm btn-secondary">취소</a>
</div>
</form>
</div>
</html>
- disabled를 사용하면 안됨! 사용하지 않으므로 값이 안넘어감
- disabled -> readonly로 변경
/controller/MemberController.java, /mail/reset-password POST매핑 메서드 작성
@PostMapping("/reset-password")
public String reset_password(@Valid MemberForm memberForm, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "member/newpassword";
}
if(!memberForm.getPassword1().equals(memberForm.getPassword2())) {
bindingResult.rejectValue("password2", "passwordInCorrect", "패스워드가 일치하지 않습니다.");
return "member/newpassword";
}
Member member = this.memberService.getMember(memberForm.getUsername()); // 현재 사용자 정보 가져오기
member.setPassword(memberForm.getPassword1()); // 패스워드 변경
this.memberService.setMember(member); // 업데이트
return "redirect:/member/login";
}
/service/MemberService.java setMember() 메서드 생성
// 비밀번호 업데이트 후 db에 저장하기 위한 메서드 - 기존 사용자 비번 초기화
public void setMember(Member member) {
member.setPassword(passwordEncoder.encode((member.getPassword()))); // 암호화한 값을 DB에 저장
this.memberRepository.save(member); // 업데이트
}
- 암호화 꼭 해줘야 함!
'Spring Boot > STUDY' 카테고리의 다른 글
[Spring Boot] React연동 프로젝트(2) - 페이징 기능 구현 (0) | 2024.07.03 |
---|---|
[Spring Boot] React연동 프로젝트(1) - 프로젝트 생성 및 리스트 출력 (1) | 2024.07.02 |
[Spring Boot] JPA 프로젝트 - 비밀번호 찾기 및 변경 기능 구현(1) (0) | 2024.06.27 |
[Spring Boot] JPA 프로젝트 - 에러페이지 작업 (0) | 2024.06.27 |
[Spring Boot] JPA 프로젝트 - AWS로 서버 연결 (1) | 2024.06.26 |