[SpringBoot] Redis+SMTP 인증메일 구현

나르·2021년 11월 17일
0

Spring

목록 보기
6/25
post-thumbnail

Redis를 springboot와 연동하여 google SMTP를 이용한 회원 비밀번호 재설정 인증메일을 구현해보았습니다.

1. 설치 및 세팅


1.1. Redis 설치

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

설치가 워낙 간단해서 그냥 다운로드했지만, 도커를 사용하는 방법도 있습니다.

1.2. redis 명령어

  • 저장
> set [key] [value]
  • 조회
// key 조회
> keys [pattern]
// 결과
> 1) "key"

패턴 *를 통해서 전체 key를 조회할 수 있습니다.

// value 조회
> get [key]
// 결과
> "value"
  • 삭제
> del [key]
  • 수정
// key 수정
> rename [key] [newKey]
// value 수정
> set [key] [value]

1.3. SpringBoot 의존성 추가

<!-- 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>

1.4. application.properties 설정 추가

# 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

2. 구현

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로, 유효할 경우 비밀번호 재설정 페이지로 리다이렉트 됩니다.

3. SMTP 용 계정 문제

해당 기능을 개발 후 실제로 테스트했을 때 Spring Mail AuthenticationFailedException 에러가 발생할 수 있습니다. 이는 구글에서 보안을 위해 인증되지 않은 접근은 차단을 해놓았기 때문입니다. 해결방법은 두가지가 있는데, 보안상 두번째 방법을 이용하는 것을 권유합니다.

  1. 이 링크에 접속 후 액세스 허용을 해서 SMTP를 사용 가능하도록 세팅하면 됩니다.

  2. 계정의 보안 설정에서 2단계 인증과 비밀번호를 설정합니다.
    문자 인증 등의 과정을 통해 2단계 인증을 활성화 한 후, 앱 비밀번호를 생성합니다. 생성된 비밀번호는 잘 복사해둔 뒤 application.properties에서 설정한 spring.mail.password={password}에 적용해주시면 됩니다.

profile
💻 + ☕ = </>

0개의 댓글