회원가입 이메일 인증 방법 개선전에는 MemberEntity에 enabled 필드를 추가하여 회원가입시 DB에 가입정보를 insert할때 enabled 필드에 false값을 넣어서 회원을 비활성화 하고 전송된 인증메일에 링크를 누르면 enabled 값을 true로 변경하여 회원을 활성화 시켰다. 하지만 이 방법은 회원이 인증을 하지 않으면 DB에 쓰레기 값만 남게 되고 회원이 다시 똑같은 아이디(email)로 회원가입을 못한다.
그래서 이메일로는 인증번호를 전송하고 회원가입 화면에서 인증번호를 제대로 기입해야 회원가입이 완료되도록 설정하였다.
인증번호 유효성은 Redis를 활용하였다.
DB를 사용안하고 Redis를 활용한 이유는 DB에 직접 접근하지 않고 Redis 캐싱을 활용하여 인증을 빨리 할수있고 데이터에 시간을 설정하여 자동으로 삭제되게 할수있는 휘발성 데이터라는 점에서 선택을 하였다.
0. 프로젝트 환경
- Spring Boot 3.2.5
- Spring Boot Data Redis 3.2.5
- Lettuce 6.3.2
1. Redis 의존성 추가와 Redis Server 설치
build.gradle에 redis 의존성을 추가한다.
//Redis 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
이 의존성을 추가하면 Redis Client인 Lettuce가 설치된다. Redis Client는 대표적으로 Jedis와 Lettuce가 있는데 SpringBoot 2.0 부터는 기본적으로 Lettuce가 사용된다.
Lettuce는 동기, 비동기및 반응형 API를 제공하는 확장 가능하고 thread-safe한 Redis 클라이언트 이다. 반면에 Jedis는 사용법이 간단하지만 thread-safe 하지 않다. 그래서 멀티 스레드 환경에서 적합하지 않다.
2. Redis Server 설치
맥북을 사용하면 brew로 쉽게 설치가 가능하다. redis-cli로 server로 접속이 가능하다.
set [key] [value] 로 데이터 인입, get [key]로 데이터 호출, del [key] 로 데이터 삭제가 가능하다. 모든 데이터를 보려면 keys * 를 사용하면된다.
3. Redis 설정 추가
application.yml에 아래 설정을 추가한다. Redis Server는 기본적으로 6379 포트 번호를 사용한다. duration은 600 기입하여 10분으로 기입하였다. 10분이 지나면 redis data는 자동으로 삭제된다.
spring:
# Redis
data:
redis:
host: localhost
port: 6379
duration: 600
4. Redis Server 연결 설정
redisConnectionFactory() 함수로 application.yml에 기입한 host와 port로 Redis Server와 연결한 Lettuce 클라이언트 객체를 생성한다.
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
@Configuration
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);
}
}
5. Redis Util 생성
Redis Server에 데이터를 생성하고 삭제하고 인증번호를 만드는 기능이 있는 Util 클래스이다.
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Random;
@RequiredArgsConstructor
@Service
public class RedisUtil {
private final StringRedisTemplate template;
@Value("${spring.data.redis.duration}")
private int duration;
public String getData(String key) {
ValueOperations<String, String> valueOperations = template.opsForValue();
return valueOperations.get(key);
}
public boolean existData(String key) {
return Boolean.TRUE.equals(template.hasKey(key));
}
public void setDataExpire(String key, String value) {
ValueOperations<String, String> valueOperations = template.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key, value, expireDuration);
}
public void deleteData(String key) {
template.delete(key);
}
public void createRedisData(String toEmail, String code) {
if (existData(toEmail)) {
deleteData(toEmail);
}
setDataExpire(toEmail, code);
}
public String createdCertifyNum() {
int leftLimit = 48; // number '0'
int rightLimit = 122; // alphabet 'z'
int targetStringLength = 6;
Random random = new Random();
return random.ints(leftLimit, rightLimit + 1)
.filter(i -> (i <= 57 || i >= 65) && (i <= 90 || i >= 97))
.limit(targetStringLength)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
}
}
StringRedisTemplate 클래스는 Spring Redis Data 에서 제공하는 클래스이다. RedisTemplate<String,String> 클래스를 상속받았고 StringRedisTemplate의 defaultSerializer는 StringRedisSerializer이다. StringRedisTemplate bean은 일반적인 String 값을 key, value로 사용하는 경우 사용하면 된다.
setDataExpire() 함수로 Redis Server에 데이터를 인입하며, application.yml에 설정한 duration값을 이용하여 10분후에 데이터가 삭제되게 한다.
createdCertifyNum() 함수는 숫자 영문자 합쳐서 랜덤으로 인증번호 6자리를 만든다.
6. mailTemplate.html 생성과 MailBuilder 클래스 생성
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div style="margin:120px">
<div style="margin-bottom: 10px">
<h1>인증 코드 메일입니다.</h1>
<br/>
<h3> 아래 코드를 사이트에 입력해주십시오</h3>
</div>
<div>
<h2 style="color: crimson;" th:text="${certifyNum}"></h2>
</div>
<br/>
</div>
</body>
</html>
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
@Service
@RequiredArgsConstructor
public class MailContentBuilder {
private final TemplateEngine templateEngine;
public String build(String certifyNum) {
Context context = new Context();
context.setVariable("certifyNum", certifyNum);
return templateEngine.process("mailTemplate", context);
}
}
Template Engine은 템플릿 양식과 데이터를 합쳐서 HTML을 보여준다. context.setVariable("certifyNum", certifyNum) 를 통해서 위에서 만든 html에서 ${certifyNum} 에 대한 값을 매핑해 줄 수 있다.
7. email 전송 Controller, Service 생성
@Operation(summary = "이메일로 인증번호 전송", description = "기입한 이메일로 인증번호를 전송합니다.")
@PostMapping("/v1/members/auth")
public void signupEmailAuthentication(@Validated @RequestBody CertifyNumRequest certifyNumRequest) {
memberServiceApplication.signupCertifyNumEmailSend(certifyNumRequest.getEmail());
}
@Override
public void signupCertifyNumEmailSend(String email) {
sendEmailAuthentication(email);
log.info("인증번호 전송 완료, email = {}", email);
}
private void sendEmailAuthentication(String toEmail) {
String certifyNum = redisUtil.createdCertifyNum();
String message = mailContentBuilder.build(certifyNum);
MailSendDTO mailSendDTO = new MailSendDTO(toEmail, "clothstar 회원가입 인증 메일 입니다.", message);
mailService.sendMail(mailSendDTO);
//메일 전송에 성공하면 redis에 key = email, value = 인증번호를 생성한다.
//지속시간은 10분
redisUtil.createRedisData(toEmail, certifyNum);
}
createdCertifyNum() 함수로 인증번호를 만들고 템플릿엔진 html 데이터를 message에 담아 인증번호 메일을 전송한다. 그리고 key는 email, value는 인증번호인 redis 데이터를 생성한다.
8. 회원가입 Service로직에 인증번호 확인 로직 추가
public Boolean verifyEmailCertifyNum(String email, String certifyNum) {
String certifyNumFoundByRedis = redisUtil.getData(email);
if (certifyNumFoundByRedis == null) {
return false;
}
return certifyNumFoundByRedis.equals(certifyNum);
}
인증번호가 틀리면 예외처리를 하고 맞으면 회원데이터를 DB에 insert 한다.
9. 회원가입 html에서 인증번호 전송 버튼을 추가하고 인증번호 메일 전송 js 추가
const certifyNumEmailSendButton = document.getElementById("certifyNum-btn");
if (certifyNumEmailSendButton) {
certifyNumEmailSendButton.addEventListener("click", (event) => {
const emailValue = document.getElementById("email").value;
if (emailValue == null || emailValue == "") {
alert("이메일을 입력해 주세요");
} else {
fetch(`/v1/members/auth`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: emailValue
}),
}).then((res) => {
if (res.ok) {
alert("인증번호가 전송 되었습니다.")
}
}).catch(() => {
console.log("catch");
});
}
});
}
10. 화면 테스트
이메일을 기입하고 인증번호 전송을 누르면 기입한 이메일로 인증 코드가 전송된다.
만약에 인증번호가 잘못되면 인증번호가 잘못 되었다고 alert창을 보여주고 인증번호를 제대로 기입하면 회원가입을 완료 시킨다.
참고
'Project > B2C-Side-Project(second)' 카테고리의 다른 글
[SpringBoot] 스프링 시큐리티 활용한 회원가입 이메일 인증 방법(개선전) (0) | 2024.06.29 |
---|---|
[SpringBoot] Github Actions 이용한 AWS EC2(ubuntu)에 자동배포와 디스코드 알림 설정 서버다운 해결 (0) | 2024.06.11 |
[SpringBoot] GlobalException 활용한 파라미터 유효성 검증 리팩토링 (0) | 2024.06.10 |
[프로젝트] 스프링시큐리티 5->6, JWT 버전 업그레이드 하고 error와 warning 수정 (0) | 2024.06.10 |
[프로젝트] DIP 지켜가며 기존 MyBatis기능에서 JPA기능으로 변경하기 (0) | 2024.06.09 |