들어가며
지금까지의 프로젝트에서 사용자의 비밀번호 변경을 서버에서 처리해 보안성이 매우 낮았습니다.
개발자조차 사용자의 개인 정보를 열람할 수 없도록 하기 위해서 비밀번호를 변경할 수 있는 링크를 사용자의 이메일로 전송하는 방법을 채택했습니다.
해당 포스팅에는 코드 부분만 나와있으며, import와 의존성 주입 등 자세한 전체 코드는 북마키에서 보실 수 있습니다.
개발 환경
Spring Boot 3 / Java 17 / Spring Security 6 / MySQL / Redis / JavaMailSender
설계 (구조와 실행 흐름)
일단 필요한 기능을 생각해봤습니다.
- 메일 생성+전송 →
JavaMailSender
- 요청 들어온 이메일 주소가 존재하는 사용자인지 확인 →
토큰 발급 전 확인
- 링크에 접근 가능한지 확인 →
일회용 토큰
- 토큰 관리(저장 · 발급 · 유효성 검증 · 무효화) →
Redis
Token
- 이메일로 전송된 링크에 무분별하게 비밀번호 변경 API 요청이 들어오는 것을 방지하기 위해 토큰을 사용합니다.
- 24시간 동안 1회 사용 가능한 일회용 토큰을 발급합니다.
Redis
- 사용자마다 개인의 토큰을 저장하기 위해 Key-Value 구조의 NoSQL DB를 사용합니다.
- 토큰 유효성 검증과 무효화를 간단히 수행할 수 있습니다.
전체적인 흐름은 아래 그림과 같습니다.
토큰 발급 + 메일 전송
🟢 build.gradle
필요한 의존성을 추가합니다.
🟢 application.yml
- Redis와 SMTP 정보들을 작성합니다.
- 저는 실제 프로젝트에서는 민감한 정보들을 숨기기 위해
application-private.yml
파일을 따로 만들어 application.yml
에 연결시키고 private 파일은 gitignore
에 등록했습니다.
- SMTP의 비밀번호는
Gmail 앱 비밀번호
를 사용하시길 추천합니다!
(Google 계정에서 2차 인증 활성화 후 생성 가능)
props
는 메일로 전송될 주소를 환경 변수로 설정합니다.
- 코드 내에 주소를 기재에 하드코딩하는 것은 추천하지 않습니다.
- 보안, 유지보수, 확장성, 테스트 등의 문제
🟢 UserController
메일 요청 API
- 로그인에 사용한 사용자의 이메일 주소를 전달하며 변경 메일 요청
🟢 UserService
메일 생성 + 전송
- 존재하는 사용자인지 확인
- 메일 생성+전송 메서드 실행
- 존재하는 사용자: 토큰
반환
- 존재하지 않는 사용자: HTTP 상태 코드 404
반환
🟢 PasswordResetRes
- 메일 요청 API의 응답 VO
🟢 MailService
환경 변수 설정 (하드코딩 X)
- yml에서 변수 변경 시 코드 내에 사용된 모든 곳에서 자동으로 변경
메일 생성
- UUID 토큰 발급
- 메일 작성
- 메일 전송 메서드 호출
메일 전송
- JavaMailSender를 이용해 전송에 필요한 정보 생성
- 데이터 형식, 보내는 사람, 받는 사람, 제목, 내용
- 전송
🟢 ResetTokenService
토큰 생성 + Redis 저장
- UUID 생성
- 식별자를 앞에 붙여 비밀번호 변경 토큰임을 표시
- 24시간 제한과 함께 토큰 저장
왜 토큰을 Key로 했나요?
토큰 유효성 확인, 무효화할 때 간단하게 Redis에서 해당 key의 존재 여부를 확인하면 되므로 O(1) 시간 복잡도로 조회 가능합니다.
토큰 유효성 검증
토큰 무효화
테스트
SMTP LOG
application.yml에서 debug: true
를 하면 로그를 볼 수 있습니다.
-
Gmail에 정상적으로 로그인
-
메일이 작성되는 과정
클라이언트가 받는 값
- 클라이언트는 토큰을 가지고 비밀번호 변경 요청을 할 수 있습니다.
정상적으로 메일이 전송됐을 때
- 임시로 작성해 하이퍼링크가 활성화되지 않은 상태입니다.
존재하지 않는 이메일로 전달됐을 때
- 코드 상에서 존재하지 않는 이메일은 API 요청을 거부하기에 이메일이 반송되는 경우는 없습니다.
- 구현 과정에서 반송된다는 것을 알게돼서 해당 내용도 추가했습니다.
토큰 검증 + 비밀번호 변경
🟢 UserController
비밀번호 변경 API
🟢 UserService
비밀번호 변경
- 토큰 유효성 검증
- 비밀번호 변경 (24시간 내, 1회 가능)
- 토큰 무효화
🟢 PasswordResetReq
- 비밀번호 변경 API의 요청 VO
테스트
변경 성공 (1회만 가능)
변경 실패
- 1회 변경 후에 동일한 토큰으로 변경 요청 시 변경에 실패하게 됩니다.
변경 Log