[3D-Asset] JAVA MailSender를 이용한 이메일 인증 보내기

HJoo·2023년 6월 22일
0

TodayStudy

목록 보기
111/111
post-thumbnail

웹 서비스의 가장 기본인 회원가입과 로그인을 구현하면서 이메일로 인증번호를 전송하여 인증하는 서비스를 구현해보았습니다.

1. 구글 앱 비밀번호 설정

  • 구글의 보안 설정
  • 2단계 인증
  • 앱 비밀번호 설정
  • 기타, 원하는 이름으로 설정
  • 생성된 16자리 앱 비밀번호 기억해서 Application.yml 세팅

2. Application.yml 세팅

spring:
  mail:
    host: smtp.gmail.com
    port: 587
    username: 전송할 구글 이메일 
    password: ${MAIL_PASSWORD}
    properties:
      mail:
        smtp:
          auth: true
          starttls:
            enable: true

3. 🌟환경변수 설정

  • 깃허브 액션을 돌릴 때 적용할 수 있는 환경변수를 시크릿을 통해 등록할 수 있다.
    깃허브 레포지토리 - Settings - Security - Secrets and variables - Actions에서 New Repository secret 클릭 후 키와 값을 입력

  • 워크플로우 파일에서 아래와 같이 설정

env: // env 섹션
  MAIL_PASSWORD: ${{ secrets.MAIL_PASSWORD }}

build-args: | // build-args 섹션
  MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
  
- name: Set environment variables for Docker Compose
  run: | // run 단계
    echo "MAIL_PASSWORD=${{ env.MAIL_PASSWORD }}" >> .env   

💡 주의할 점 💡
처음에는 이메일도 환경변수로 등록해서 사용하려 했다. 하지만 계속 로그인이 되지 않는 에러 발생... 혹시나 하고 이메일을 명시했더니..성공
예측 : 깃허브 시크릿은 특수문자를 인식하지 못한다!
결론 : 이메일은 명시할 수 밖에 없다..

4. 컨트롤러

  • 로그인시 비밀번호 찾기, 회원가입시 이메일 인증에 이메일 전송 기능 사용
    @MyLog
    @PostMapping("/login/send")
    public ResponseEntity<?> sendPasswordChangeCode(@RequestBody @Valid UserRequest.SendCodeInDTO sendCodeInDTO, Errors errors){
        UserResponse.SendCodeOutDTO sendCodeOutDTO = userService.sendPasswordChangeCodeService(sendCodeInDTO);
        ResponseDTO<?> responseDTO = new ResponseDTO<>(sendCodeOutDTO);
        return ResponseEntity.ok().body(responseDTO);
    }
    
    @MyLog
    @PostMapping("/signup/send")
    public ResponseEntity<?> sendSignupCode(@RequestBody @Valid UserRequest.SendCodeInDTO sendCodeInDTO, Errors errors){
        UserResponse.SendCodeOutDTO sendCodeOutDTO = userService.sendSignupCodeService(sendCodeInDTO);
        ResponseDTO<?> responseDTO = new ResponseDTO<>(sendCodeOutDTO);
        return ResponseEntity.ok().body(responseDTO);
    }

5. 서비스

    public UserResponse.SendCodeOutDTO sendPasswordChangeCodeService(UserRequest.SendCodeInDTO sendCodeInDTO){
        User userPS = findValidUserByEmail(sendCodeInDTO.getEmail());
        if(!userPS.getFirstName().equals(sendCodeInDTO.getFirstName()) || !userPS.getLastName().equals(sendCodeInDTO.getLastName())){
            throw new Exception400("name", "잘못된 요청입니다. ");
        }
        userPS.generateEmailCheckToken();
        String html = createPasswordChangeHTML(userPS);
        ClassPathResource imageResource = new ClassPathResource("static/logo.jpg");
        try {
            MailHandler mailHandler = new MailHandler(javaMailSender);
            mailHandler.setTo(sendCodeInDTO.getEmail());
            mailHandler.setSubject("3D 에셋 스토어, 비밀번호 재설정을 위한 이메일 인증");
            mailHandler.setText(html, true);
            mailHandler.setInline("logo", imageResource);
            mailHandler.send();
        } catch (Exception e) {
            throw new Exception500("이메일 전송 실패 : " + e.getMessage());
        }
        return new UserResponse.SendCodeOutDTO(userPS.getId());
    }


    @Transactional
    public UserResponse.SendCodeOutDTO sendSignupCodeService(UserRequest.SendCodeInDTO sendCodeInDTO) {
    
        String html = createSignupHTML(user);
        ClassPathResource imageResource = new ClassPathResource("static/logo.jpg");
        try {
            MailHandler mailHandler = new MailHandler(javaMailSender);
            mailHandler.setTo(sendCodeInDTO.getEmail());
            mailHandler.setSubject("3D 에셋 스토어, 회원가입을 위한 이메일 인증");
            mailHandler.setText(html, true);
            mailHandler.setInline("logo", imageResource);
            mailHandler.send();
        } catch (Exception e) {
            throw new Exception500("이메일 전송 실패 : " + e.getMessage());
        }
        return new UserResponse.SendCodeOutDTO(user.getId());
    }

6. HTML 생성 메서드

  • 서비스 코드에 포함해도 되지만 따로 작성
    // 회원가입 메일 본문 생성 메서드
    private String createPasswordChangeHTML(User user){
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy년 mm월 dd일 HH시 mm분 ss초");
        LocalDateTime expiredTime = user.getEmailCheckTokenCreatedAt().plusMinutes(10);
        String expiredTimeStr = expiredTime.format(formatter);

        return "<body>\n" +
                "    <table\n" +
                "      style=\"\n" +
                "        background-color: #ffffff;\n" +
                "        width: 630px;\n" +
                "        height: 572px;\n" +
                "        padding: 5%;\n" +
                "      \"\n" +
                "    >\n" +
                "      <tr>\n" +
                "        <td style=\"text-align: center;\">\n" +
                "          <img src='cid:logo' style='width: 225px; height: 54px;' />"+
                "          <hr\n" +
                "            style=\"\n" +
                "              width: 100%;\n" +
                "              height: 1px;\n" +
                "              background-color: #9fadbc;\n" +
                "              margin: 32px 0 32px 0;\n" +
                "            \"\n" +
                "          />\n" +
                "          <div style=\"height: 358px; width: 566px;\">\n" +
                "            <h1>Neuroid Asset 비밀번호 재설정 인증</h1>\n" +
                "            <p>안녕하세요. Neuroid Asset Store입니다!</p>\n" +
                "            <p>\n" +
                "              아래 인증 코드를 입력하면 비밀번호 재설정을 하실 수 있습니다.\n" +
                "            </p>\n" +
                "            <h2>인증 코드: [" + user.getEmailCheckToken()+ "]</h2>\n" +
                "            <strong\n" +
                "              >인증 코드는 대소문자를 구분합니다. 정확히 입력해 주세요.</strong\n" +
                "            >\n" +
                "            <p>\n" +
                "              인증메일의 유효기간 내에 인증이 완료되지 않으면 인증이 취소됩니다.\n" +
                "            </p>\n" +
                "          </div>\n" +
                "        </td>\n" +
                "      </tr>\n" +
                "    </table>\n" +
                "  </body>";
    }

7. 메일 전송 화면

profile
안녕하세요. Chat JooPT입니다.

0개의 댓글