3/31 TIL - 점프투스프링부트 추가 기능 구현

큰모래·2023년 3월 31일
0

점프투스프링부트


3-15 (5) 비밀번호 찾기와 변경

목표

  • 사용자가 비밀번호를 분실했을 때 가입할 때 적은 이메일 주소로 임시 비밀번호 발송 기능 추가
  • 로그인 후 임시 비밀번호와 새 비밀번호를 입력받아 비밀번호를 변경 기능 추가

SMTP 개념

전자메일을 보내기 위한 프로토콜

인터넷에서 전자메일을 보내기 위해 가장 많이 사용되는 프로토콜 중 하나이다.


SMTP 서버 메일 설정

  • SMTP 서버 메일을 Gmail 이나 Naver 등으로 설정해야 한다.
  • 네이버 메일을 서버 메일로 할 것이므로, 이에 따른 네이버 메일에서의 환경설정이 필요하다.
  • POP3/SMTP를 사용함으로 설정
  • SMTP 포트와 서버명을 기억해두고 application.properties(yml)에 적용해야 한다.

application.properties

  • 네이버 메일 환경설정에서 찾은 정보를 토대로 설정
#smtp 설정
spring.mail.host=smtp.naver.com // SMTP 서버명
spring.mail.port=465  // SMTP 포트번호
spring.mail.username=[서버메일주소] 
spring.mail.password=[서버메일비밀번호]
spring.mail.properties.debug=true // 디버그모드 활성화
spring.mail.properties.mail.smtp.auth=true  // SMTP 인증을 통해 메일 서버에 로그인 가능
spring.mail.properties.mail.smtp.ssl.enable= true  //SSL 연결 사용
spring.mail.properties.mail.smtp.starttls.enable=true  //SMTP STARTTLS 명령을 사용하여 암호화된 연결
spring.mail.properties.mail.smtp.ssl.trust=smtp.naver.com  //메일 서버의 인증서를 신뢰할 수 있는 인증서로 등록

build.gradle

  • 스프링부트를 통해 이메일을 전송하려면 해당 디펜던시를 추가해야한다.
implementation 'org.springframework.boot:spring-boot-starter-mail'

MailForm

  • 서버에서는 MailForm 객체 형식으로 보낼 메시지를 작성한다.
@Getter
@Setter
public class MailForm {

    private String address;
    private String title;
    private String message;
}

PasswordUpdateForm

  • 임시비밀번호 생성 후, 다시 원하는 비밀번호로 변경을 위한 PasswordUpdateForm 생성
  • 임시 비밀번호 1번, 새로 만들 비밀번호 2번을 입력받아야 한다.
@Getter
@Setter
public class PasswordUpdateForm {
    @NotEmpty(message = "기존 비밀번호는 필수항목입니다.")
    String oldPassword;

    @NotEmpty(message = "새로운 비밀번호는 필수항목입니다.")
    String newPassword1;

    @NotEmpty(message = "새로운 비밀번호 확인은 필수항목입니다.")
    String newPassword2;
}

UserRepository - 추가

  • 이메일을 통해 SiteUser 객체에 접근해야 하므로 findByEmail 메서드를 추가했다.
Optional<SiteUser> findByEmail(String email);

UserService - 추가

  • createMailForm
    • createRandomPassword 메서드를 통해 랜덤 생성된 6자리 숫자를 임시 비밀번호로 지정
    • updateTempPassword를 통해 해당 계정의 비밀번호를 임시 비밀번호로 업데이트한다.
    • MailForm 객체를 만들어 보낼 주소,제목,내용을 설정한다.
  • updateTempPassword
    • 매개변수로 받은 메일을 토대로 DB에 저장된 유저를 찾는다.
    • 해당 유저의 비밀번호를 임시 비밀번호로 초기화한다. (이때 passwordEncoder 를 통한 인코딩 필수!)
  • updatePassword
    • 임시비밀번호를 발급받은 유저가 원하는 비밀번호로 세팅할 때 사용할 메서드
    • 동작 방식은 updateTempPassword와 동일
  • createRandomPassword
    • 자바의 Math.random()을 통해 임의의 6자리 난수를 생성하여 리턴한다.
  • sendEmail
    • 매개변수로 받은 MailForm 객체를 토대로 메시지를 생성한다.
    • 이때, 메시지는 SimpleMailMessage 객체를 통해 다양한 메시지 내용 설정이 가능하다.
    • 주입받은 JavaMailSender 객체(mailSender)를 통해 최종적으로 메일을 보낸다.
		public MailForm createMailForm(String email) {
        String password = createRandomPassword();
        updateTempPassword(password, email);

        MailForm mailForm = new MailForm();
        mailForm.setAddress(email);
        mailForm.setTitle("임시비밀번호 안내 메일입니다.");
        mailForm.setMessage("회원님의 임시 비밀번호는 " + password + "입니다. 감사합니다");

        return mailForm;
    }

    public void updateTempPassword(String password, String email) {
        SiteUser user = userRepository.findByEmail(email).get();
        user.setPassword(passwordEncoder.encode(password));
    }

    public void updatePassword(String password, SiteUser user) {
        user.setPassword(passwordEncoder.encode(password));
    }

    public String createRandomPassword() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 6; i++) {
            sb.append((int) (Math.random() * 10));
        }

        return sb.toString();
    }

		private final JavaMailSender mailSender; //@RequiredArgsConstructor를 통해 주입받음

    public void sendEmail(MailForm mailForm) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(mailForm.getAddress());
        message.setSubject(mailForm.getTitle());
        message.setText(mailForm.getMessage());
        message.setFrom("on8214@naver.com");
        message.setReplyTo("on8214@naver.com");
        mailSender.send(message);
    }

UserController - 추가

  • /forgotPassword
    • 비밀번호 찾기 버튼을 누르면 /user/forgot_password 폼으로 이동
    • 스프링 시큐리티 설정에서 모든 페이지에 대한 권한을 로그인으로 해놨기 때문에 @PreAuthorize 설정을 isAnonymous()로 설정했다. (비밀번호 찾기는 로그인 없이 볼수 있어야하므로)
  • /sendEmail
    • 비밀번호 찾기 페이지에서 이메일 폼을 전송했을 때 요청되는 메서드
    • 사용자가 입력한 이메일을 @RequestParam을 통해 매개변수로 받는다.
    • userServicecreateMailForm에 이메일을 넣어 MailForm 객체를 반환받는다.
    • userServicesendEmailMailForm 객체를 넣어 최종적으로 메일 발신
    • 메일이 발신되면 로그인창으로 리다이렉트 시켜준다.
  • GET - /updatePassword
    • 비밀번호 변경 폼으로 이동
    • PasswordUpdateForm을 매개변수로 넣어 비밀번호 변경 폼의 입력과 필드 매칭을 시킬 수 있다.
  • POST - /updatePassword
    • 비밀번호 변경 버튼 클릭 시 메서드 실행
    • @Validated를 통해 PasswordUpdateForm 객체가 정상적으로 들어왔는지 확인
    • 에러가 있다면 BindingResult 객체에 에러가 담겼다면 비밀번호 변경 폼으로 다시 이동
    • 신규 비밀번호와 신규 비밀번호 확인 입력값이 달라도 비밀번호 변경 폼으로 다시 이동
    • 위의 에러들을 모두 통과하면 현재 접속자의 username을 통해 유저 객체를 찾고 비밀번호 초기화
		@PreAuthorize("isAnonymous()")
    @GetMapping("/forgotPassword")
    public String findPassword() {

        return "/user/forgot_password";
    }

    @PreAuthorize("isAnonymous()")
    @PostMapping("/sendEmail")
    public String sendEmail(@RequestParam String email) {
        MailForm mailForm = userService.createMailForm(email);
        userService.sendEmail(mailForm);

        return "redirect:/user/login";
    }

    @GetMapping("/updatePassword")
    public String updatePassword(PasswordUpdateForm passwordUpdateForm) {
        return "/user/update_password";
    }

    @PostMapping("/updatePassword")
    public String updatePassword(@Validated PasswordUpdateForm passwordUpdateForm, BindingResult bindingResult, Principal principal) {
        if (bindingResult.hasErrors()) {
            return "/user/update_password";
        }

        if (!passwordUpdateForm.newPassword1.equals(passwordUpdateForm.newPassword2)) {
            return "/user/update_password";
        }

        SiteUser user = userService.getUser(principal.getName());
        userService.updatePassword(passwordUpdateForm.newPassword1, user);

        return "redirect:/user/login";
    }

최종 결과 화면 - (html은 화면 캡처본을 통해 대체하는걸로…)

  • 비밀번호 찾기 버튼
  • 비밀번호 찾기 페이지
  • 임시비밀번호 발송 메일
  • 비밀번호 변경 페이지
    - 비밀번호 변경 시 쿼리 로그를 통해 정상적으로 조회 및 수정을 한 것을 확인할 수 있다.


고찰

  1. 컨트롤러와 서비스 사이에서 어떤 식으로 데이터를 주고 받을지 뇌정지가 많이 옴.
  2. UserControllerUserService에 너무 많은 역할이 생긴 느낌??
    1. 메일전송과 비밀번호 재설정에 대한 도메인을 따로 만드는 게 나았을지도 모르겠다.
  3. 주말에 여유가 있으면 개발한 내용을 토대로 테스트도 진행해보자
profile
큰모래

0개의 댓글