지금까지의 프로젝트에서 사용자의 비밀번호 변경을 서버에서 처리해, 보안성이 매우 낮았습니다.
개발자도 사용자의 개인 정보를 열람할 수 없도록 하기 위해 BCryptPasswordEncoder
로 암호화하여 저장했습니다. 하지만 이를 복호화할 수 없어, 새 비밀번호로 변경해야 했습니다.
안전하게 변경하기 위해 비밀번호 변경 링크를 사용자의 이메일로 전송하는 방법을 도입하고, 링크에 접속하는 자의 신원 확인을 위해 Token을 발급했습니다.
해당 포스팅에는 코드 부분만 나와있으며, import와 의존성 주입 등 자세한 전체 코드는 북마키에서 보실 수 있습니다.
개발 환경
Spring Boot 3 / Java 17 / Spring Security 6 / MySQL / Redis / JavaMailSender
일단 필요한 기능을 생각해봤습니다.
JavaMailSender
토큰 발급 전 확인
일회용 토큰
Redis
전체적인 흐름은 아래 그림과 같습니다.
필요한 의존성을 추가합니다.
application-private.yml
파일을 따로 만들어 application.yml
에 연결시키고 private 파일은 gitignore
에 등록했습니다.Gmail 앱 비밀번호
를 사용하시길 추천합니다!props
는 메일로 전송될 주소를 환경 변수로 설정합니다. 사용자의 이메일 주소를 전달하며 비밀번호 변경 메일을 요청합니다.
이메일 전송을 위해 필요한 절차는 크게 3가지입니다.
resetTokenService
호출)mailService
호출)sendResetEmailWithToken
메서드는 컨트롤러에서 호출되며, 내부 로직은 단일 메서드로 분리하고 private으로 정의해 외부로부터 보호합니다.
generateEmail
메서드를 호출하면 메일 작성과 전송이 수행되며, 내부 로직은 단일 메서드로 분리했습니다.TOKEN_PREFIX
토큰을 Key로 설정한 이유?
토큰 유효성 확인, 무효화할 때 간단하게 Redis에서 해당 key의 존재 여부를 확인하면 되므로 O(1) 시간 복잡도로 조회 가능합니다.
토큰 생성 & 무효화 메서드에
@Transactional
을 붙인 이유?
- 비밀번호 변경과 토큰 무효화를 하나의 트랜잭션 내에서 수행하여, 원자성을 보장하기 위함입니다.
- 두 작업이 모두 성공하거나 하나라도 실패하면 모두 롤백되도록 하여, 일관성 있는 처리가 이루어지도록 하고자 했습니다.
application.yml에서 debug: true
를 하면 로그를 볼 수 있습니다.
Gmail에 정상적으로 로그인
메일이 작성되는 과정
비밀번호 변경 요청을 전달했을 때, 실패할 경우는 토큰 유효성 실패이므로, 해당 내용을 반환합니다.
비밀번호 변경을 위한 VO (username, password, token)
컨트롤러에서 resetPwWithToken
을 호출하면
를 수행하는 각 단일 메서드를 호출하고, 성공 시 로그를 출력합니다.
private
으로 정의해 외부로부터 보호합니다.