오늘은 어제에 만든 이메일 인증 코드를 수정, 보완하여 개선했다. > 이전 포스팅: 회원가입시 이메일인증 구현하기
사용자가 인증번호를 받고 그 인증번호를 다시 입력해서 회원가입을 완료 한다. 이 때 인증번호와 사용자를 비교하기 위해 email을 다시 한번 입력해야했다.
회원가입 할 때 이미 email을 입력했는데, 다시 email을 입력하라고? 정말 불편하다.
그래서 쿠키를 사용하여 인증번호만 입력하도록 수정했다.
아예 쿠키에 모든 정보를 넣어보내면 EmailAuth를 따로 생성 할 필요가 없지 않을까? 라는 고민을 했었다. 그렇지만 쿠키에 민감한 정보인 email은 물론이고 password를 넣어보낼 수는 없기에, nickname만 넣어서 비교하기로 결정했다.
우선 이메일 인증 메일을 발송 받을 때 nickname이 담긴 Cookie를 발행한다.
Cookie 또한 5분의 만료시간을 갖게 만들었다.
public void checkAndSendVerificationCode(String nickname, String password, String email, HttpServletResponse response) {
// 인증번호 보낸 내역이 있는지 확인
if (Boolean.TRUE.equals(redisTemplate.hasKey(nickname))) {
throw new IllegalArgumentException("해당 이메일 주소로 인증번호가 이미 발송되었습니다.");
}
// 인증번호 메일 보내기
String sentCode = sendVerificationCode(email);
// redis 활용
redisTemplate.opsForValue().set(nickname, sentCode, 5*60*1000, TimeUnit.MILLISECONDS);
// 쿠키에 인증할 nickname을 넣어보냄
Cookie cookie = getCookieByNickname(nickname);
setCookie(cookie, response);
emailAuthRepository.save(new EmailAuth(sentCode, nickname, passwordEncoder.encode(password), email));
}
}
private Cookie getCookieByNickname(String nickname) {
Cookie cookie = new Cookie(NICkNAME_AUTHORIZATION_HEADER, nickname);
cookie.setPath("/");
cookie.setMaxAge(5*60);
return cookie;
}
사용자가 쿠키를 가진 상태에서 인증번호를 입력하여 회원가입 완료를 시도한다. 쿠키의 값인 nickname을 받아와서 DB의 EmailAuthRepository에서 nickname에 해당하는 emailAuth 데이터를 찾아온다.
이때 버그가 생겼었는데, 5분 내에 인증이 안되면 emailAuth가 중복되어 생성되기 때문에 무엇을 찾아올지 몰라 오류가 생기는 것이었다.
따라서 쿼리문은 가장 최근 만들어진 것을 찾아오도록 만들었다.
Optional<EmailAuth> findTopByNicknameOrderByCreatedAtDesc(String nickname);
public EmailAuth verifyVerificationCode(String nickname, String verificationCode) {
// 가장 최근에 만들어진 인증 데이터 조회 (5분 이내 인증에 실패했을 경우 중복 생성 될 수 있음)
var emailAuth = emailAuthRepository
.findTopByNicknameOrderByCreatedAtDesc(nickname)
.orElseThrow(()
-> new IllegalArgumentException("인증 가능한 nickname이 아닙니다."));
...
}
남겨진 데이터는 오류 및 정보 누출을 일으키기 충분하므로 삭제시켜줘야한다.
기존에 DB의 데이터를 삭제하는 것은 물론이고, 발행된 쿠키의 HEADER값으로 다시 쿠키를 발행하여 이를 해결했다.
값을 null로 하고, 만료 시간을 0으로 설정하여 바로 삭제되게 만들었다.
public void endEmailAuth(EmailAuth emailAuth, HttpServletResponse response) {
redisTemplate.delete(emailAuth.getNickname());
emailAuthRepository.delete(emailAuth);
Cookie cookie = new Cookie(NICkNAME_AUTHORIZATION_HEADER, null);
cookie.setMaxAge(0);
cookie.setPath("/");
response.addCookie(cookie);
}
정신없이 기능 개발을 하다보니 메서드나 필드가 맞지 않는 곳에 선언되어 있었다. 또한 redisTemplate
같이 특정 레이어에서만 필요한 Bean도 모든 여기저기서 주입되어 사용되고 있었다.
따라서 회원가입시 일반적인 회원가입시 필요한 User정보는 관리하는 것들을 UserService에, Email과 관련된 기능들은 EmailAuthService로 모아서 정리하여 이를 해결했다.
또한 필요한 메서드를 선언하거나 나눠서 알아보기 쉽게 만들었다.
마지막으로 아래와 같이 문자열이 들어가는 부분들을 상수로 처리했다.
(문자열을 상수로 처리하는 이유는 혹시 모를 오타를 방지하며, 추후 수정을 용이하게 만들기 위함이다.)
public static final String NICkNAME_AUTHORIZATION_HEADER = "NicknameAuth";
사실 이미 위에서는 수정된 코드들로 포스팅하고 있었다!
마지막으로 리팩토링까지 완료하니 코드를 따라가며 읽을 때 문맥상 이해하기 쉽고 보수도 용이해졌다.
이제 다른사람이 코드리뷰 하기에 좀 편해졌겠지!?