카테고리 없음
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