
회원가입에 이메일 인증 기능을 구현하려고 한다.
구현해야 하는 로직은 다음과 같다.
우선은 유저에게 요청 받은 이메일로 인증번호를 전송해야한다. 이메일을 전송할때는 메일을 보낼때 사용되는 프로토콜인 SMTP(Simple Mail Transfer Protocol)을 이용한다.
SMTP 메일 send 구조

gmail,naver등 메일 서비스를 하는 기업들은 각자의 SMTP 서버를 가지고 있다. 우리는 이 서버들을 이용하여 메일을 보내야 한다.
네이버메일을 이용하여 기능을 구현해 보자.
우선 네이버메일에서 SMTP설정을 해야한다.
POP3/SMTP 설정

implementation 'org.springframework.boot:spring-boot-starter-mail:2.7.1'
SMTP를 사용하는 메일에 대한 정보와 SMTP 설정을 한다.
@Configuration
public class EmailConfig {
@Value("${email.setForm}")
private String email;
@Value("${email.password}")
private String password;
@Bean
public JavaMailSender mailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost("smtp.naver.com");
mailSender.setPort(465);
mailSender.setUsername(email);
mailSender.setPassword(password);
Properties javaMailProperties = new Properties();
javaMailProperties.put("mail.transport.protocol", "smtp");
javaMailProperties.put("mail.smtp.auth", "true");//smtp 서버에 인증이 필요
javaMailProperties.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");//SSL 소켓 팩토리 클래스 사용
javaMailProperties.put("mail.smtp.starttls.enable", "true");//STARTTLS(TLS를 시작하는 명령)를 사용하여 암호화된 통신을 활성화
javaMailProperties.put("mail.debug", "false");
javaMailProperties.put("mail.smtp.ssl.trust", "smtp.naver.com");//smtp 서버의 ssl 인증서를 신뢰
javaMailProperties.put("mail.smtp.ssl.protocols", "TLSv1.2");//사용할 ssl 프로토콜 버젼
mailSender.setJavaMailProperties(javaMailProperties);
return mailSender;
}
}
@Service
@RequiredArgsConstructor
public class EmailService {
private final JavaMailSender mailSender;
private final RedisService redisService;
@Value("${email.setFrom}")
private String setFrom;
private static final String EMAIL_TITLE = "회원 가입을 위한 인증 이메일";
private static final String EMAIL_CONTENT_TEMPLATE = "아래의 인증번호를 입력하여 회원가입을 완료해주세요."+
"<br><br>" +
"인증번호 %s";
public String joinEmail(String email) {
String code = Integer.toString(makeRandomNumber());
String content = String.format(EMAIL_CONTENT_TEMPLATE, code);
mailSend(setForm, email, EMAIL_TITLE,content);
return code;
}
public void mailSend(String setFrom, String toMail, String title, String content) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message,true,"utf-8");
helper.setFrom(setFrom);
helper.setTo(toMail);
helper.setSubject(title);
helper.setText(content,true);
mailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}
public int makeRandomNumber() {
Random r = new Random();
StringBuilder randomNumber = new StringBuilder();
for(int i = 0; i < 6; i++) {
randomNumber.append(r.nextInt(10));
}
return Integer.parseInt(randomNumber.toString());
}
}
인증번호 메일 전송 기능은 완성 되었다. 이제 인증번호가 일치하는지에 대한 기능을 구현 해야한다. 해당 기능을 구현하려면 특정 메일로 보낸 인증번호를 서버에서 인지하고 있어야 한다.
인증번호는 짧은시간 동안만 유효하고 그 후에는 필요 없어지기에 현재 사용중인 MySQL같이 정형화되고 무거운 RDB에 저장하면 비효율적이다. 짧은 기간동안만 저장이 가능하고 RAM에 저장하여 빠른속도로 접근이 가능한 Redis를 이용하고자 한다.
Redis는 Remote Dictionary Server의 약자로 키(Key) - 값(Value) 쌍의 해시 맵과 같은 구조를 가진 비관계형(NoSQL) 데이터베이스 관리 시스템(DBMS)이다.
오픈 소스 기반으로 인-메모리(In-memory) 데이터 구조 저장소로 메모리에 데이터를 저장한다.
Redis는 별도의 쿼리문이 존재하지 않기에 복잡하지 않고 빠르게 저장,읽기를 위한 데이터를 관리할때 용이하다.
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
Spring에서 redis를 사용하기 위해선 RedisTemplate,RedisRepository 두가지 방법을 제공하는데 여기선 RedisTemplate를 이용하여 구현한다.
@RequiredArgsConstructor
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
//key,value를 Email,code로 구현할거기에 둘다 String으로 직렬화 해도 문제없다.
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(redisConnectionFactory());
return redisTemplate;
}
}
@Service
@RequiredArgsConstructor
public class RedisService {
private final RedisTemplate<String, Object> redisTemplate;
//email을 key값 code를 value로 하여 3분동안 저장한다.
public void setCode(String email,String code){
ValueOperations<String, Object> valOperations = redisTemplate.opsForValue();
//만료기간 3분
valOperations.set(email,code,180, TimeUnit.SECONDS);
}
//key값인 email에 있는 value를 가져온다.
public String getCode(String email){
ValueOperations<String, Object> valOperations = redisTemplate.opsForValue();
Object code = valOperations.get(email);
if(code == null){
throw new UnAuthenticationException(ResponseMessage.UN_AUTHORIZED);
}
return code.toString();
}
}