Redis를 springboot와 연동하여 google SMTP를 이용한 회원 비밀번호 재설정 인증메일을 구현해보았습니다.
Mac
$ brew install redis
$ brew services start redis
$ brew services stop redis
$ brew services restart redis
$ redis-cli
Ubuntu 20.04 TS
# apt-get update
$ sudo apt-get update
$ sudo apt-get upgrade
# redis 설치
$ sudo apt-get install redis-server
# redis.conf에서 원격접속, 메모리 정책 등을 수정할 수 있다.
$ sudo vi /etc/redis/redis.conf
# 변경된 설정 적용
$ sudo systemctl restart redis-server.service
# 포트 확인
$ netstat -nlpt | grep 6379
$ redis-cli
설치가 워낙 간단해서 그냥 다운로드했지만, 도커를 사용하는 방법도 있습니다.
> set [key] [value]
// key 조회
> keys [pattern]
// 결과
> 1) "key"
패턴 *를 통해서 전체 key를 조회할 수 있습니다.
// value 조회
> get [key]
// 결과
> "value"
> del [key]
// key 수정
> rename [key] [newKey]
// value 수정
> set [key] [value]
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- SMTP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
# redis
spring.redis.host=localhost
spring.redis.port=6379
# reids pool
spring.redis.pool.max-idle=8
spring.redis.pool.min-idle=0
spring.redis.pool.max-active=8
spring.redis.pool.max-wait=-1
# Email Send Configuration_SMTP
spring.mail.host=smtp.gmail.com
spring.mail.port=587
spring.mail.username={id}
spring.mail.password={password}
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.starttls.enable=true
Redis에 데이터를 추가하고 읽어올 클래스를 생성합니다.
RedisUtil.java
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.time.Duration;
@RequiredArgsConstructor
@Service
public class RedisUtil {
private final StringRedisTemplate stringRedisTemplate;
public String getData(String key){
ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
return valueOperations.get(key);
}
public void setData(String key, String value){
ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set(key,value);
}
public void setDataExpire(String key,String value,long duration){
ValueOperations<String,String> valueOperations = stringRedisTemplate.opsForValue();
Duration expireDuration = Duration.ofSeconds(duration);
valueOperations.set(key,value,expireDuration);
}
public void deleteData(String key){
stringRedisTemplate.delete(key);
}
}
Spring boot starter mail의 경우 JavaMailSender 클래스를 가져와서 사용합니다.
비밀번호 재설정 이메일을 보내고, 인증 링크가 유효한지 판별할 서비스를 생성합니다.
회원가입 시 입력한 이메일 주소로만 링크를 받을 수 있으며, 링크의 경우 인증링크 확인 api주소+uuid로 생성한 인증키 형식으로 지정해줬습니다. 또한 인증 링크는 expireDuration을 설정해 30분 동안만 유효하도록 함으로써 보안적인 측면을 강화했습니다.
VerifyEmailService.java
@RequiredArgsConstructor
@Service
public class VerifyEmailService{
private final UserRepository userRepository;
private final RedisUtil redisUtil;
@Autowired
private JavaMailSender emailSender;
public void sendMail(String to,String sub, String text){
SimpleMailMessage message = new SimpleMailMessage();
message.setTo(to);
message.setSubject(sub);
message.setText(text);
emailSender.send(message);
}
public void sendVerificationMail(String email) throws NotFoundException {
String VERIFICATION_LINK = "http://localhost:8080/api/user/verify/";
if(email==null) throw new NotFoundException("멤버가 조회되지 않음");
UUID uuid = UUID.randomUUID();
// redis에 링크 정보 저장
redisUtil.setDataExpire(uuid.toString(),email, 60 * 30L);
// 인증 링크 전송
sendMail(email,"비밀번호 재설정 인증메일입니다.",VERIFICATION_LINK+uuid.toString());
}
public void verifyEmail(String key) throws NotFoundException {
String memberEmail = redisUtil.getData(key);
if(memberEmail==null) throw new NotFoundException("유효하지 않은 링크입니다.");
redisUtil.deleteData(key);
}
}
이제 컨트롤러를 만들어줍니다.
VerifyController.java
@RestController
@RequiredArgsConstructor
@RequestMapping(value="/api/user")
public class VerifyController {
private final VerifyEmailService verifyEmailService;
private final UserService userService;
// 비밀번호 재설정 이메일 전송
@GetMapping("/verify")
public ResponseEntity verify(@RequestParam String email) throws NotFoundException {
User user = userService.searchUserByEmail(email);
if(user!=null) {
userPasswordResetVerifyEmailService.sendVerificationMail(user.getEmail());
return new ResponseEntity(HttpStatus.OK);
}
else return new ResponseEntity(HttpStatus.NO_CONTENT);
}
// 인증링크 확인
@GetMapping("/verify/{key}")
public ResponseEntity getVerify(@PathVariable String key) throws NotFoundException {
try {
userPasswordResetVerifyEmailService.verifyEmail(key);
URI redirectUri = new URI("http://{재설정 path}");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(redirectUri);
return new ResponseEntity<>(httpHeaders, HttpStatus.SEE_OTHER);
}
catch (Exception e) {
return new ResponseEntity(HttpStatus.BAD_REQUEST);
}
}
}
http://localhost:8080/api/user/verify
로 회원가입시 입력한 이메일을 보내면 해당 계정으로 인증링크가 동봉된 메일이 도착합니다. 인증링크로 접근하면 유효성을 판별해 유효하지 않을 경우에는 404로, 유효할 경우 비밀번호 재설정 페이지로 리다이렉트 됩니다.
해당 기능을 개발 후 실제로 테스트했을 때 Spring Mail AuthenticationFailedException
에러가 발생할 수 있습니다. 이는 구글에서 보안을 위해 인증되지 않은 접근은 차단을 해놓았기 때문입니다. 해결방법은 두가지가 있는데, 보안상 두번째 방법을 이용하는 것을 권유합니다.