๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Project/์‹นํ‹”์›€

[์‹นํ‹”์›€] 11/07 ๊ฐœ๋ฐœ์ผ์ง€ ํšŒ์›๊ฐ€์ž…์‹œ ์ด๋ฉ”์ผ ์ธ์ฆ ๋ฐ›๊ธฐ 2 : ์ด๋ฉ”์ผ ์ธ์ฆ์ฝ”๋“œ ๊ฒ€์ฆ

by ํ•œ33 2024. 11. 10.

๐Ÿ’ก ๋ชฉํ‘œ

์ด๋ฉ”์ผ ์ธ์ฆ๋ฒˆํ˜ธ๋ฅผ ๋ฐ›์œผ๋ฉด ํšŒ์›๊ฐ€์ž…์„ ํ•˜๊ณ ์ž ํ•˜๋Š” ์œ ์ €๋Š” 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 ์ธ ๊ฒฝ์šฐ์— ํšŒ์›๊ฐ€์ž…์„ ์ •์ƒ์ ์œผ๋กœ ์ง„ํ–‰์‹œํ‚จ๋‹ค.


๐Ÿ’ก ๊ณผ์ •

 

๋‹ค์Œ ๊ธ€

https://hanstory33.tistory.com/260