개요
회원가입의 마지막 단계에는 이메일 인증을 완료해야 한다.
이메일 인증 도입 이유
- 이메일을 회원의 아이디로 사용하기 때문에 고유한 값이 필요(Unique Key)
- 이미 타 사이트에서 인증이 된 이메일을 사용함으로써 고유성을 확보
하지만
이메일 인증의 방식에는 두가지가 존재
일반적으로 내가 경험한 사이트들은 회원가입시 이메일로 인증 코드를 발송하고, 인증 코드를 사이트에 입력하여 일치여부를 확인한 뒤 가입을 완료시킨다.
위와 같은 인증 코드를 입력시키는 일반적인 방법 외에 인증 링크를 클릭해서 인증을 완료하는 방법도 존재하였기에 이 두가지 방식 중에 어떤 방식을 적용 시킬지 고민을 하였다.
링크 vs 입력
인증링크를 클릭하는 방식을 선택
가입 시 입력한 이메일에 쿼리 파라미터로 이메일과 인증코드가 담긴 링크를 전송하여 해당 링크를 클릭하면 서버가 보내준 코드와 일치하는지 확인 후 회원가입을 완료한다.
인증링크를 클릭하는 방식이 코드를 입력하는 방식보다 더 좋다고 생각한 이유는 다음과 같다.
- 링크를 클릭하는 방법은 코드를 입력하는 별도의 행위가 존재하지 않으므로 사용자의 편의성을 증가시킴
- 또한 form을 입력하는 별도의 화면이 필요하지 않기 때문에 개발자의 편의성도 증가시킬 것이라고 판단
- 인증코드를 훨씬 복잡하게 만들어도 유저는 입력할 필요가 없으며 인증 링크가 유일한 엑세스가 가능하도록 하기 때문에 보안성이 강화될 수 있다.
인증코드 저장
- 처음에는 USER테이블에 인증코드를 같이 저장하는 방식으로 생각
- USER 테이블에서 조회를 할 때 인증코드는 필요하지 않는데 계속 조회가 되기 때문에 같은 테이블에 담는 것이 적절하지 않다고 판단
- USER_AUTH 테이블을 생성하고 이메일과 인증코드를 복합고유키로 지정하여 저장
기능 설계 및 구현
이메일 인증의 흐름
- 회원가입 화면에서 가입하려는 이메일을 입력 후 회원가입 버튼 클릭
- 클라이언트 서버에게 유저의 이메일로 인증 링크 전송 요청
- 서버는 랜덤 번호 생성 및 인증번호와 이메일을 USER_AUTH 테이블에 저장 후 사용자의 이메일로 인증 링크 전송
- 사용자는 자신의 메일함에 클라이언트로부터 전송된 메일 속 인증 링크를 클릭
- 인증 링크 속 메일과 인증번호가 DB에 저장된 메일과 인증번호가 일치하면 유저를 활성화상태로 변경
구현
회원가입 코드
public User join(UserForm user) {
// 이메일 중복 검사를 진행한다.
Optional<User> optUser = userRepository.findByEmail(user.getEmail());
if (optUser.isPresent()) {
throw new DuplicatedEmailException(user.getEmail());
}
// 입력한 이메일 주소로 메일을 보내고 return값으로 code를 가져온다.
String code = mailService.sendMail(user.getEmail());
// USER_AUTH 테이블에 코드와 이메일주소를 저장한다.
userAuthRepository.save(UserAuth.builder().email(user.getEmail())
.code(code)
.build());
// 비활성화 상태로 회원가입을 완료한다.
return userRepository.save(User.builder()
.email(user.getEmail())
.password(passwordEncoder.encode(user.getPassword()))
.name(user.getName())
.disabled(true)
.build());
}
메일 보내기
public MimeMessage createMail(String mail) {
// java.util.Random을 사용해서 인증코드 만들기
getCode();
// 메일 내용 작성
MimeMessage message = javaMailSender.createMimeMessage();
try {
message.setFrom(senderEmail);
message.setRecipients(MimeMessage.RecipientType.TO, mail);
message.setSubject("이메일 인증");
String body = "";
body += "<h3> 아래의 인증링크를 클릭하여 회원가입을 마무리하세요. </h3>";
// 이메일과 링크가 포함된 인증링크를 생성
body += "<h2>" +
"<a href='http://localhost:8081/user/mail/confirm?email=" +
mail + "&code=" + code + "' target='_blank'> 이메일 인증 확인</a>" +
"</h2>";
message.setText(body, "UTF-8", "html");
} catch (MessagingException e) {
e.printStackTrace();
}
return message;
}
public String sendMail(String mail) {
MimeMessage message = createMail(mail);
javaMailSender.send(message);
return code;
}
이메일 확인
// 링크의 파라미터를 추출해 이메일과 인증코드를 비교
public String emailConfirm(String email, String code) {
Optional<UserAuth> optUserAuth = userAuthRepository.findById(email);
UserAuth userAuth = optUserAuth.get();
// 코드가 같으면 USER 테이블에서 해당 이메일로 데이터를 찾은 뒤 활성화 상태로 변경
if(code.equals(userAuth.getCode())) {
Optional<User> optUser = userRepository.findByEmail(email);
User user = optUser.get();
user.setDisabled(false);
userRepository.save(user);
return "활성";
};
return "인증실패";
}
개선 사항
- 인증이 완료되면 해당 데이터를 삭제하여 USER_AUTH 테이블에 불필요한 데이터가 계속 쌓이는 것을 방지 할 것
- 계속 제거를 해야 되는 기능을 사용하는 것 대신에 만료시간이 설정 가능한 Redis를 사용할 것
- 만료시간을 짧게 설정한다면 인증 링크의 유효시간도 설정이 가능하기 때문에 보안성이 더 높아질 가능성이 있음
'멋진 개발자 > 트러블슈팅' 카테고리의 다른 글
트러블 슈팅 - 준비된 재고는 200개인데 240명이 구매를 성공한 건에 대하여 (0) | 2024.03.15 |
---|---|
트러블 슈팅 - api-gateway에서 jwt 인증/인가 구현 (0) | 2024.03.03 |
트러블 슈팅 - RefreshToken을 활용한 보안성 높이기 (0) | 2024.03.03 |