카테고리 없음

TIL 이메일 설정

creator7087 2025. 4. 14. 12:23

📘 오늘 학습한 주요 내용

✅ 1. 이메일 인증 코드 전송 (sendEmail)

  • 사용자 이메일로 6자리 인증 코드를 전송.
  • 인증 코드는 Redis에 5분간 저장.
  • 세션에는 auth_status를 "PENDING"으로 설정하여 인증 대기 상태를 표시.

팁:
Redis의 TTL(Time To Live)을 이용해 인증코드 유효시간을 쉽게 관리할 수 있음. 인증 처리에 실패한 경우를 대비하여 세션 상태로 인증 흐름을 추적할 수 있음.

 

더보기
    public void sendEmail(AuthByEmailRequestDto dto, HttpServletRequest request){
        String email=dto.getEmail();

        String code = createCode();

        SimpleMailMessage simpleMailMessage = new SimpleMailMessage();

//        String ip=request.getRemoteAddr();

        HttpSession session=request.getSession();

        session.setAttribute("auth_status", "PENDING");

        redisTemplate.opsForValue().set(email,code,5, TimeUnit.MINUTES);

        try{
            simpleMailMessage.setTo(email);
            simpleMailMessage.setSubject("InXj 이메일 인증 번호 입니다.");
            simpleMailMessage.setText("인증코드: "+code);
            javaMailSender.send(simpleMailMessage);
        } catch (MailException e) {
            throw new RuntimeException(e);
        }


    }

 

✅ 2. 이메일 존재 여부 확인 (existEmail)

  • 주어진 이메일이 이미 가입된 상태인지 여부를 체크.
  • 회원 가입 시 이메일 중복 방지 용도로 활용.

팁:
Repository의 existsByEmail()은 boolean 반환으로 빠른 존재 체크가 가능하므로 중복체크에 효율적임.

더보기
public String existEmail(AuthByEmailRequestDto dto){
    if (userRepository.existsByEmail(dto.getEmail())){
        return "회원가입된 이메일 입니다.";
    }else {
        return "회원가입 가능한 이메일 입니다.";
    }
}

 

 

✅ 3. 비밀번호 재설정 (findPassword)

  • 이메일 인증 코드를 확인한 후, 새 임시 비밀번호를 발급.
  • 새 비밀번호는 사용자 이메일로 전송되고, PasswordEncoder로 암호화하여 저장.

팁:
SecureRandom을 사용하여 보안 수준이 높은 랜덤 비밀번호 생성이 가능하며, 사용자를 위해 임시 비밀번호 안내 메시지에 변경 요청도 함께 포함

 

더보기
@Transactional
public void findPassword(CheckingByEmailRequestDto dto){

    String email=dto.getEmail();
    String code= dto.getCode();

    String savedCode = redisTemplate.opsForValue().get(email);

    SimpleMailMessage simpleMailMessage = new SimpleMailMessage();

    String newPassword=createPassword();
    if (code.equals(savedCode)){
        simpleMailMessage.setTo(email);
        simpleMailMessage.setSubject("새로운 비밀번호 입니다");
        simpleMailMessage.setText("비밀번호:" + newPassword+"\n 최대한 빠르게 변경 해주세요");
        User user=userRepository.findByEmail(email).
                orElseThrow(()->new BaseException(ErrorCode.NOT_FOUND_EMAIL));
        user.setPassword(securityConfig.passwordEncoder().encode(newPassword));
        javaMailSender.send(simpleMailMessage);

    }else{
        throw new BaseException(INVALID_CODE);
    }
}

✅ 4. 이메일 인증 확인 (CheckEmailAuth)

  • 세션 상태와 Redis에 저장된 인증 코드를 비교하여 인증 처리.
  • 인증 성공 시 이메일에 대해 "email:verified" 값을 Redis에 저장 (10분간 유효).
  • 인증 완료 후 세션 무효화.

팁:
email:verified라는 키 네이밍 규칙은 이메일 인증 상태를 분리 추적하는 데 유용함. 세션 상태와 Redis 상태를 함께 활용하면 이중 체크가 가능

 

더보기
public void CheckEmailAuth(CheckingByEmailRequestDto dto,HttpServletRequest request){
    String email=dto.getEmail();
    String code= dto.getCode();

    HttpSession session=request.getSession(false);

    String attribute=(String) session.getAttribute("auth_status");

    if (!attribute.equals("PENDING")){
        throw new RuntimeException("해커세요?");
    }


    String savedCode = redisTemplate.opsForValue().get(email);
    System.out.println("savedCode = " + savedCode);
    System.out.println("code = " + code);

    if (code.equals(savedCode)) {
        redisTemplate.opsForValue().set(email + ":verified", "true", 10, TimeUnit.MINUTES);
        session.invalidate();
    }
    else {
        throw new BaseException(INVALID_CODE);
    }
}

 

✅ 5. 인증코드 및 비밀번호 생성 유틸

  • createCode(): 6자리 숫자 인증 코드 생성.
  • createPassword(): 특수문자를 포함한 10자리 임시 비밀번호 생성.

💡

팁:
SecureRandom.getInstanceStrong()은 OS의 엔트로피를 기반으로 하는 강력한 난수 생성기이므로 보안 민감 정보 생성에 적합함.

더보기
private String createCode() {
    int length = 6;
    try {
        Random random = SecureRandom.getInstanceStrong();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < length; i++) {
            builder.append(random.nextInt(10));
        }
        return builder.toString();
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException(e);
    }
}
private String createPassword() {
    int length = 10;
    String lower = "abcdefghijklmnopqrstuvwxyz";
    String upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    String digits = "0123456789";
    String special = "!@#$%^&*";
    String allChars = lower + upper + digits + special;

    try {
        Random random = SecureRandom.getInstanceStrong();
        StringBuilder password = new StringBuilder();

        // 최소 1개씩 강제 포함
        password.append(lower.charAt(random.nextInt(lower.length())));
        password.append(upper.charAt(random.nextInt(upper.length())));
        password.append(digits.charAt(random.nextInt(digits.length())));
        password.append(special.charAt(random.nextInt(special.length())));

        // 나머지 문자 채우기
        for (int i = 4; i < length; i++) {
            password.append(allChars.charAt(random.nextInt(allChars.length())));
        }

        return password.toString();
    } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("SecureRandom 생성 실패", e);
    }
}

✅ 6.  인증 확인 및 회원가입 진행:

  • 회원가입 전에 Redis에 저장된 "email:verified" 값을 조회.
  • 인증되지 않았거나 TTL 만료로 값이 없을 경우 회원가입을 차단.

 팁
Redis TTL은 시간이 지나면 데이터가 사라지기 때문에, 사용자가 인증을 미루거나 브라우저를 닫으면 인증이 만료될 수 있음. 이 경우에는 인증 재요청 흐름을 프론트엔드에서 유도하는 UX 설계도 함께 고려해야 함.

더보기
public void signUp(SignUpRequestDto dto) {
    String verifiedKey = dto.getEmail() + ":verified";
    String verified = redisTemplate.opsForValue().get(verifiedKey);

    if (!"true".equals(verified)) {
        throw new BaseException(UNAUTHORIZED_CODE);  // 이메일 인증 안됨 예외
    }

    if (userRepository.existsByEmail(dto.getEmail())) {
        throw new EmailConflictException();
    }

    if (userRepository.existsByUsername(dto.getUsername())) {
        throw new BaseException(ErrorCode.CONFLICT_STATUS);
    }



    //비밀번호 인코딩
    String encodedPassword = passwordEncoder.encode(dto.getPassword());

    User newUser = User.builder()
            .email(dto.getEmail())
            .password(encodedPassword)
            .username(dto.getUsername())
            .name(dto.getName())
            .birthday(dto.getBirthday())
            .gender(dto.getGender())
            .phoneNumber(dto.getPhoneNumber())
            .profileImageUrl(dto.getProfileImageUrl())
            .build();

    userRepository.save(newUser);
}

✅ 설정

더보기
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}
#SMTP
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username=이메일 
spring.mail.password=앱 비밀번호
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true

# Redis ??
spring.data.redis.host=localhost
spring.data.redis.port=6379