비밀번호 재설정에서 본인 인증을 부분을 개발하기 위해 SMTP를 사용했습니다.
아이디의 경우 찾기 기능을 통해서 찾을 수 있지만, 비밀번호의 경우 “BCryptPasswordEncoder”를 통해서 인코딩 후 저장하기 때문에 복호화 해서 사용자에게 제공하는 것은 사실상 불가능합니다.
그래서 사용자가 본인 인증을 하면 비밀번호를 재설정할 수 있도록 비즈니스 로직을 짰습니다.
여기서 본인인증을 어떻게 할지 정해야 했는데, 휴대폰 인증이나 인증서 같은 방법은 간단한 프로젝트에 어울리지 않는다고 생각했고, 간단하고 무료인 SMTP를 사용하기로 했습니다.
그중에서도 가장 널리 쓰이는 Gmail을 선택했습니다.
SMTP를 사용하기 위해서는 2단계 인증을 완료해야 합니다.
구글 메인 > 계정 (우상단 메뉴) > 보안 (좌측 메뉴) > 2단계 인증

인증을 완료한 상태에서 [2단계 인증] 내의 [앱 비밀번호]를 클릭합니다.

저는 프로젝트를 진행하면서 만들어 놓은 비밀번호가 존재합니다.
하지만 하나 더 만들어 보도록 하겠습니다.
아래의 앱 이름에 사용할 앱의 이름을 넣고 만들기를 누르면 비밀번호가 생성됩니다.
예시로 하나 만들어 보겠습니다.

이렇게 만들어진 비밀번호를 이용해 메일을 전송할 수 있습니다.
(생성 후 바로 삭제했기 때문에 보안 문제는 없습니다.)
이런 비밀번호들은 깃허브에 올리면 보안 상에 문제가 될 수 있으므로 꼭 .gitginore 파일에서 깃허브에 올리지 않기로 설정한 파일 내부에서 관리해야 합니다.
이 부분은 추후에 다시 업로드해서 링크를 걸어두도록 하겠습니다.
아래 포스트로 업로드 해놓았으니 잘 모르시는 분들은 참고하시기 바랍니다.
https://velog.io/@snhng/%EC%BA%A1%EC%8A%A4%ED%86%A4-SpringBoot%EC%97%90%EC%84%9C-%EB%B9%84%EB%B0%80-%EB%B2%88%ED%98%B8-%EA%B4%80%EB%A6%AC-.gitignore-.properties
먼저 SMTP를 사용하기 위해서는 gradle 파일에 의존성을 추가해 줘야 합니다.
dependencies { implementation group: 'org.springframework.boot', name: 'spring-boot-starter-mail', version: '3.0.5' }
그리고 application.yml 파일에 다음을 추가합니다.
spring: mail: host: smtp.gmail.com port: 587 username: 전송에_사용_할_이메일 password: ${EMAIL_AUTH} properties: mail: smtp: auth: true starttls: enable: true required: true connectiontimeout: 5000 timeout: 5000 writetimeout: 5000
- port : 구글에서 사용하는 SMTP 포트번호
- username : 전송에 사용 할 이메일 (honggildong@gmail.com)
- password : 이전에 구글에서 생성한 앱 비밀번호, 따로 비밀번호 관리 파일에 저장
- starttls : 보안 기능
가독성을 위해 일부 코드를 수정, 삭제했습니다. 코드 전문은 아래의 깃허브에서 확인하실 수 있습니다. (특히 optional null check)
생성한 OTP를 저장할 객체
public class EmailAuthCode { private String address; private String authCode; private LocalDateTime createdAt; }Controller
// 비밀번호 확인을 위한 이메일 인증 1 // 인증번호 전송 @PostMapping("/ ... ") public ResponseEntity<Map<String, String>> verificationEmailRequest(@RequestBody EmailRequestDTO dto) { Map<String, String> result = new HashMap<>(); if (! memberService.sendCodeToEmail(dto.getInputId())) { return addErrorStatus(result); } return addOkStatus(result); } // 비밀번호 확인을 위한 이메일 인증 2 // 인증번호 확인 @PostMapping("/ ... ") public ResponseEntity<Map<String, String>> verificationEmail(@RequestBody VerificationEmailDTO dto) { Map<String, String> result = new HashMap<>(); if (!memberService.verifiedCode(dto.getInputId(), dto.getInputCode())) { return addErrorStatus(result); } return addOkStatus(result); } // 비밀번호 확인을 위한 이메일 인증 3 @PostMapping("/ ... ") public ResponseEntity<Map<String, String>> resetMemberPassword(@RequestBody ResetPasswordDTO dto) { Map<String, String> result = new HashMap<>(); if (!memberService.resetMemberPassword(dto)) { return addErrorStatus(result); } return addOkStatus(result); }Service
// 이메일을 보내는 메서드 public boolean sendCodeToEmail(String memberId) { Optional<Member> foundMember = memberRepository.findById(memberId); String address = foundMember.get().getEmail(); String title = "SafeGuard 이메일 인증 번호"; String authCode = createCode(); mailService.sendEmail(address, title, authCode); Optional<EmailAuthCode> foundCode = emailAuthCodeRepository.findById(memberId); if (foundCode.isPresent()) { emailAuthCodeRepository.delete(foundCode.get()); } emailAuthCodeRepository.save(new EmailAuthCode(address, authCode, LocalDateTime.now())); return true; } // OTP를 생성하는 메서드 private String createCode() { int length = 6; Random random = new Random(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < length; i++) { builder.append(random.nextInt(10)); } return builder.toString(); } // OTP를 확인하는 메서드 public boolean verifiedCode(String memberId, String authCode) { Optional<Member> foundMember = memberRepository.findById(memberId); Optional<EmailAuthCode> foundCode = emailAuthCodeRepository.findById(foundMember.get().getEmail()); if (Duration.between( foundCode.get().getCreatedAt(), LocalDateTime.now()).getSeconds() > emailAuthCodeDuration ) { return false; } return authCode.equals(foundCode.get().getAuthCode()); } // 재설정한 비밀번호로 변경하는 코드 @Transactional public boolean resetMemberPassword(ResetPasswordDTO dto) { Optional<Member> foundMember = memberRepository.findById(dto.getId()); foundMember.get().setPassword(passwordEncoder.encode(dto.getPassword())); return true; }


간단하게 이메일로 본인 인증을 할 수 있도록 구현할 수 있었습니다.