카테고리 없음

Implement Email Auth #01

porkbellyYam 2023. 2. 8. 23:07

RedisConfig.java

package dev.aco.back.Utils.Redis;

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.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {
    @Value("${dev.aco.redishost}")
    private String host;

    @Value("${dev.aco.redisport}")
    private int port;

    @Bean
    RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(host);
        config.setPort(port);
        return new LettuceConnectionFactory(config);
    }

    @Bean
    RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(connectionFactory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(String.class));

        return redisTemplate;
    }
}

RedisManager.java

package dev.aco.back.Utils.Redis;

import java.time.Duration;
import java.util.Optional;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;

import dev.aco.back.Entity.User.Member;
import dev.aco.back.Repository.MemberRepository;
import dev.aco.back.Utils.JWT.JWTManager;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class RedisManager {
    private final RedisTemplate<String, String> redisTemplate;
    private final MemberRepository mrepo;
    private final JWTManager manager;

    @Transactional
    public void setRefreshToken(String token, Long mid) {
        ValueOperations<String, String> refreshToken = redisTemplate.opsForValue();
        refreshToken.set(token, mid.toString(), Duration.ofDays(7L));
        mrepo.loggedMember(mid, true);
    }

    @Transactional
    public void removeRefreshToken(String refreshToken) {
        Optional<String> mid = Optional.ofNullable(redisTemplate.opsForValue().get(refreshToken));
        if (mid.isPresent()) {
            mrepo.loggedMember(Long.parseLong(mid.get()), false);
            redisTemplate.delete(refreshToken);
        }
    }

    @Transactional
    public Boolean TokenValidator(String accessToken, String refreshToken) {
        Optional<Member> targetMember = mrepo
                .findById(Long.parseLong(manager.tokenParser(accessToken).get("userNumber").toString()));
        Long accessTokenMemberId = targetMember.orElse(Member.builder().memberId(0L).build()).getMemberId();
        Long refreshTokenMemberId = Long
                .parseLong(Optional.ofNullable(redisTemplate.opsForValue().get(refreshToken)).orElse("-1"));
        if (refreshTokenMemberId == accessTokenMemberId & manager.tokenValidator(refreshToken)
                & isLogged(accessToken)) {
            return true;
        } else {
            removeRefreshToken(refreshToken);
            mrepo.loggedMember(refreshTokenMemberId, false);
            return false;
        }
    }

    public Boolean refreshTokenChecker(String refreshToken) {
        return Optional.ofNullable(redisTemplate.opsForValue().get(refreshToken)).isPresent() ? true : false;
    }

    public Boolean isLogged(String aToken) {
        return mrepo.findById(Long.parseLong(manager.tokenParser(aToken).get("userNumber").toString()))
                .orElse(Member.builder().memberId(0L).logged(false).build()).getLogged();
    }


    // 이메일인증 (저장, 유효기간 설정, 삭제)
    // Redis에 들어가는 DATA (Key : authnum / Value : email)
    public String getEmailData(String key) { // authnum 을 통해 email을 얻는다.
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        return valueOperations.get(key);
    }

    public void setEmailData(String key, String value) { // authnum 을 통해 email을 얻는다.
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        valueOperations.set(key, value);
    }

    public void setDataExpire(String key, String value, long expiration) {
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        // expireDuaration 동안 (key, value)를 저장한다.
        Duration expireDuration = Duration.ofSeconds(expiration);
        valueOperations.set(key, value, expireDuration);
    }

    public void deleteEmailData(String key) {
        // 데이터 삭제
        redisTemplate.delete(key);
    }
}

내가 이해하고 작성한 Redis를 이용한 이메일 인증의 기본적인 흐름

  1. 사용자가 회원가입을 하려고 request를 보내면 EmailController를 통해mailService.sendEmail을 호출합니다.

  2. 여기선 인증번호 생성과 해당 이메일이 mailRepository에 존재하는지 확인합니다. 만약 존재한다면 이메일, 인증번호와 인증여부(default = false)를 저장합니다.

  3. 해당정보는 Redis에게도 보내집니다. RedisManger를 통해 유효기간 또한 셋팅합니다.

  4. AcoMailSender.send() 매서드를 호출하며 메일을 보냅니다.

  5. 메일인증 검사는 EmailController가 요청에 따라 verifyMail을 호출하며 이는 MailService에서 동작됩니다.

  6. 보내온 정보들이 Redis 에 들어있는지 확인하기 위해 redisManger.getEmailData를 호출합니다.

  7. delete를 이용해 비워줍니다.

  8. if 조건문을 사용해 결과에 따라 true(인증성공), false(인증실패)를 반환합니다.