회원가입시 이메일 인증을 할 때 사용자는 loginId, password, email 등을 입력한다.
이메일에 받은 인증코드를 입력 할 때 이러한 정보를 다시 입력하는 일 없이 회원가입이 완료된다.
이 때 입력한 데이터를 어디에, 어떻게 저장하고 넘겨주어야 할까?
보안은 괜찮을지도 고민해보았다.
생각해야하는 것은 데이터 저장 및 읽기, 유효시간, 보안!
단순히 쿠키에 정보를 넣어 보내는 것은 사용자의 개인정보를 노출 시킬 수 있는 위험한 행동이다. 따라서 데이터의 안전을 위해 서버측의 DB를 활용하는 것이 좋겠다.
인증 정보는 임시 데이터이므로 5분간 유효하고, 그 이후에는 삭제하자.
쿠키를 활용하여 회원가입을 하려는 당사자인지 한번 더 확인하는 것은 어떻까?
인증코드를 발송 할 때 loginId를 쿠키에 넣어보낸다면 아래 두가지 장점이 있을 것이다.
- 해당 쿠키의 존재유무로 인증코드 발송을 요청한 클라이언트가 맞는지 확인 가능
- 서버에서는 loginId를 활용하여 DB에 손쉽게 접근 가능
인증 과정이 복잡하므로 @Transactional 을 사용하자.
회원정보 임시 저장에 활용할 DB는 Redis로 결정하였다.
@Transactional
어노테이션은 DB의 일관성과 안정성을 위해 계획했다.
@Transacional
을 사용하면 모든 작업을 하나의 트랜잭션으로 처리한다. 따라서 만약 중간에 문제가 발생한다면 전체 메서드가 롤백되어 잘못된 회원 가입 정보가 입력되는 것을 방지 할 수 있다.
인증 번호와 함께 입력한 정보를 Redis에 임시저장하며, 5분의 유효시간을 설정한다.
이 때 EmailAuth 객체를 생성하여 저장했다.
public void setSentCodeByLoginIdAtRedis(EmailAuth emailAuth) {
String loginId = emailAuth.getLoginId();
emailAuthRepository.saveEmailAuth(loginId, emailAuth);
}
public void saveEmailAuth(String key, EmailAuth emailAuth) {
redisTemplate.opsForValue().set(key, emailAuth);
redisTemplate.expire(key, 5 * 60, TimeUnit.SECONDS);
}
앞에서 설계했던 대로 loginId를 갖고 있는 쿠키를 클라이언트에 보내어 2차 확인을 할 수 있게 만들었다. (쿠키 또한 5분의 유효시간을 갖는다.)
@PostMapping("/signup")
public ResponseEntity<ApiResponse> signup(...) {
userService.signup(signupRequestDto);
String loginId = signupRequestDto.getLoginId();
Cookie cookie = emailAuthService.getCookieByLoginId(loginId);
response.addCookie(cookie);
return ResponseEntity.ok(new ApiResponse<>("인증 번호를 입력해주세요.", HttpStatus.OK.value()));
}
FE에서 인증코드를 헤더에 넣어보내기로 했기 때문에 request header에서 verificationCode
를 받아줬다.
쿠키에서 loginId를 받아오며, 클라이언트에게 해당 쿠키가 없다면 인증 과정은 더 이상 진행되지 않는다.
이후 인증 정보를 확인하는 verificateCode
메서드를 실행한다.
@GetMapping("/signup")
public ResponseEntity<ApiResponse> verificateCode(
HttpServletRequest request,
HttpServletResponse response,
@CookieValue(EmailAuthService.LOGIN_ID_AUTHORIZATION_HEADER) String loginId) {
String verificationCode = request.getHeader(EmailAuthService.VERIFICATION_CODE_HEADER);
UserResponseDto userResponseDto = userService.verificateCode(verificationCode, loginId);
emailAuthService.removeloginIdCookie(response);
return ResponseEntity.ok()
.body(new ApiResponse("회원가입 성공", HttpStatus.OK.value(), userResponseDto));
}
verificateCode
메서드에서 코드가 동일한지 확인 후 회원 정보를 main DB에 저장한다.
인증이 완료되면 임시 데이터는 삭제한다.
앞서 계획한대로 verificateCode
메서드에 @Transactional
어노테이션은 DB의 일관성과 안정성 유지를 도모했다.
@Transactional
public UserResponseDto verificateCode(String verificationCode, String loginId) {
EmailAuthDto emailAuthDto = emailAuthService.checkVerifyVerificationCode(loginId,
verificationCode);
... // 입력 정보 확인
User user = new User(loginId, nickname, email, password, firstPreferredCategory,
secondPreferredCategory);
userRepository.save(user);
// 인증 완료되면 Redis의 임시 데이터 삭제
emailAuthService.concludeEmailAuthentication(loginId);
return new UserResponseDto(user);
}
key는 loginId이며, value가 아래와 같이 나타난다.
객체로 저장했기에 JSON 형태로 출력되는 것을 볼 수 있다.
{"@class":"com.example.jujuassembly.domain.user.emailAuth.entity.EmailAuth","loginId":"asdzxc123123","nickname":"qsdqwe123123","email":"robbie@naer.com","password":"$2a$10$BYykFjajtC/5osd4Oq4ace1gSh3k5y/Xzlpaf32dMgLY64I1uJShi","firstPreferredCategoryId":3,"secondPreferredCategoryId":4,"sentCode":"664099"}
쿠키가 잘 발행되었다.
Redis의 데이터와 쿠키는 삭제되었다.
데이터가 잘 들어가서 회원가입이 완료되었다.
이메일 인증에 5분의 유효시간, 재입력 방지, 보안 문제라는 요구사항들을 충족시키며 기능을 구현 할 수 있었다.