๐ก ๋ชฉํ
์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ์ผ๋ฉด ํ์๊ฐ์ ์ ํ๊ณ ์ ํ๋ ์ ์ ๋ 10๋ถ์์ ์ธ์ฆ๋ฒํธ๋ฅผ ์ ๋ ฅํด์ผํ๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค๊น์ง ์ ๊ทผ์ ํ ํ์๊ฐ ์๊ธฐ ๋๋ฌธ์ redis ๋ฅผ ํ์ฉํด์ TTL ๊ธฐ๋ฅ์ ๋ฃ์ด ์๊ฐ ์ ํ์ ๊ฑธ์๋ค.
์ด๋ฉ์ผ ์ธ์ฆ์ ๋ฐ์ง ์์ ์ํ์์ ๊ฐ์ ํ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ์ธ์ฆ๋ฒํธ๋ฅผ ๋ฐ๋๋ก ์ค์ ๋ ํ์ํ๋ค.
EmailCertificationController
@PostMapping("/v2/auth/verify-certification")
@Operation(summary = "์ธ์ฆ๋ฒํธ ํ์ธ", description = "๋ฐ์ ์ธ์ฆ๋ฒํธ๋ฅผ ํ์ธํ๋ API")
@ApiResponse(responseCode = "200", description = "์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋์์ต๋๋ค.")
public ResponseEntity<CommonResponse<String>> verifyCertificationNumber(@Valid
@RequestBody
@Parameter(description = "์ธ์ฆ๋ฒํธ ํ์ธ ์ ๋ณด ์
๋ ฅ")
VerifyCertificationNumberRequestDto requestDto
) {
return ResponseEntity.ok(CommonResponse.success(emailCertificationService.verifyCertificationNumber(requestDto)));
}
์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ๋ฅผ ํ์ธํ๊ธฐ ์ํด์ ์ด๋ฉ์ผ๊ณผ ์ธ์ฆ๋ฒํธ๊ฐ ๋ด๊ธด Dto ๋ฅผ ๋ง๋ค์ด์ Service ๋ก ๋ณด๋๋ค.
์ด ๊ณผ์ ์ [์ธ์ฆ๋ฒํธ ํ์ธ] ๋ฒํผ์ด ๋๋ฆฌ๋ฉด ์์ฒญ์ด ๋ณด๋ด์ง api ๋ค.
EmailCertificationService
@Slf4j
@Service
@RequiredArgsConstructor
public class EmailCertificationService {
private final EmailConfig emailConfig;
private final RedisTemplate<String, Object> redisTemplate;
private final UserService userService;
public String emailCertification(EmailCertificationRequestDto requestDto) {
// ์
๋ ฅํ ์ด๋ฉ์ผ๋ก ์ค๋ณต ์ ๋ฌด ํ์ธ
String email = requestDto.getEmail();
boolean isExisted = userService.existsByEmail(email);
if (isExisted) throw new DuplicateEmailException();
// 4์๋ฆฌ ์ธ์ฆ๋ฒํธ ์์ฑ
String certificationNumber = getCertificationNumber();
// Redis ์ ์ด๋ฉ์ผ๊ณผ ์ธ์ฆ๋ฒํธ๋ฅผ ์ ์ฅ (10๋ถ ์ ํจ๊ธฐ๊ฐ ์ค์ )
redisTemplate.opsForValue().set(email + ":code", certificationNumber, 10, TimeUnit.MINUTES);
redisTemplate.opsForValue().set(email + ":verified", false, 10, TimeUnit.MINUTES);
// ์ด๋ฉ์ผ ๋ฐ์
CompletableFuture<Boolean> isSuccessed = emailConfig.sendCertificationEmail(email, certificationNumber);
// ์ด๋ฉ์ผ ๋ฐ์ก ์ฑ๊ณต ์ ๋ก๊ทธ ๊ธฐ๋ก
log.info("์ด๋ฉ์ผ ์ธ์ฆ๋ฒํธ ์ ์ก ์๋ฃ: {}", certificationNumber);
}).exceptionally(ex -> {
// ์์ธ ์ฒ๋ฆฌ: ์ด๋ฉ์ผ ๋ฐ์ก ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ฉด ๋ก๊น
log.error("์ด๋ฉ์ผ ๋ฐ์ก ์ค ์ค๋ฅ ๋ฐ์: {}", email, ex);
return null;
});
return "์ด๋ฉ์ผ์ด ์ ์์ ์ผ๋ก ์ ์ก๋์์ต๋๋ค.";
}
// ์ธ์ฆ๋ฒํธ ํ์ธ
public String verifyCertificationNumber(VerifyCertificationNumberRequestDto requestDto) {
String email = requestDto.getEmail();
String storedCertificationNumber = (String) redisTemplate.opsForValue().get(email + ":code");
if (storedCertificationNumber == null || !storedCertificationNumber.equals(requestDto.getInputCertificationNumber())) {
throw new InvalidVerificationCodeException();
}
// ์ธ์ฆ ์ฑ๊ณต ์ ์ธ์ฆ ์ํ๋ฅผ true ๋ก ๋ณ๊ฒฝ
redisTemplate.opsForValue().set(email + ":verified", true, 10, TimeUnit.MINUTES);
return "์ธ์ฆ์ด ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋์์ต๋๋ค.";
}
// 4์๋ฆฌ ์ธ์ฆ๋ฒํธ ์์ฑ ๋ฉ์๋
public static String getCertificationNumber() {
String certificationNumber = "";
for (int count = 0; count < 4; count++) {
certificationNumber += (int) (Math.random() * 10);
}
return certificationNumber;
}
}
์ธ์ฆ๋ฒํธ๊ฐ ์์ฑ๋๋ฉด์ redis ์ 10๋ถ ์ ํจ๊ธฐ๊ฐ์ผ๋ก ํด๋น email ์ code ๊ฐ์ด ์ ์ฅ๋๊ณ , ์ดํ ํ์๊ฐ์ ์ ๊ฒ์ฆ ํ์ธ์ ํ๊ธฐ ์ํด verified ๊ฐ๋ false ๋ก ์ค์ ํด์ ์ถ๊ฐํด์คฌ๋ค.
์ดํ์ ์ธ์ฆ๋ฒํธ ํ์ธ ๊ณผ์ ์์ redis ์ ์ ์ฅ๋์ด์๋ code ๊ฐ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ฝ๋์ ๋์ผํ๋ฉด verified ๊ฐ์ true ๋ก ๋ฐ๊ฟ์ค๋ค.
AuthService
// ํ์๊ฐ์
public SignupResponseDto signup(SignupRequestDto signupRequestDto) {
// ์ด๋ฉ์ผ ์ธ์ฆ ์ฌ๋ถ ํ์ธ
Boolean isVerified = (Boolean) redisTemplate.opsForValue().get(signupRequestDto.getEmail() + ":verified");
if (isVerified == null || !isVerified) {
throw new EmailNotVerifiedException();
}
String encodedPassword = passwordEncoder.encode(signupRequestDto.getPassword());
UserRole userRole = UserRole.of(UserRole.ROLE_USER.getUserRole());
User newUser = new User(
signupRequestDto.getEmail(),
encodedPassword,
signupRequestDto.getUserName(),
signupRequestDto.getBirthYear(),
userRole,
null
);
User savedUser = userRepository.save(newUser);
String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), savedUser.getUserName(), userRole);
return new SignupResponseDto(bearerToken);
}
์ดํ์ ๊ฐ์ ํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด verified ๊ฐ์ด ์๊ฑฐ๋ flase ์ธ ๊ฒฝ์ฐ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๊ณ true ์ธ ๊ฒฝ์ฐ์ ํ์๊ฐ์ ์ ์ ์์ ์ผ๋ก ์งํ์ํจ๋ค.
๐ก ๊ณผ์
๋ค์ ๊ธ