Redis 이용한 다중 로그인 관리 - 2

Kim Dong Kyun·2023년 7월 28일
1

개요 - 지난 시간 이야기

1. RedisDAO는 변경하지 않으려고 생각했다.

RedisDAO 로 거의 인증 관련 일을 처리하고 있긴 하지만, 캐싱에서 혹시라도 RedisTemplate 사용할 일이 있을 수 있다고 판단해서, RedisAuthDAO 라는 인증용 DAO를 만들기로 결정했다.

RedisDAO 모습

@Repository
public class RedisAuthDAO {

    private final HashOperations<String, String, String> hashOperations;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public RedisAuthDAO(RedisTemplate<String, String> redisTemplate, HashOperations<String, String, String> hashOperations) {
        this.hashOperations = redisTemplate.opsForHash();
    }

    /**
     *
     * @param userEmail 레디스 키
     * @param browser 해쉬 키
     * @param refreshToken 해쉬 키의 대응되는 밸류
     * @param expiration 만료 시간 (Duration.ofMills 로 스태틱하게 사용합니다)
     */
    public void storeRefreshToken(String userEmail, String browser, String refreshToken, Duration expiration) {
        List<Object> refreshTokenData = new ArrayList<>(3);
        refreshTokenData.add(browser);
        refreshTokenData.add(refreshToken);
        refreshTokenData.add(expiration.toMillis());
        try {
            String refreshTokenDataString = objectMapper.writeValueAsString(refreshTokenData);
            // List<Object> 형태를 String으로 형변환 (Serializer)
            hashOperations.put(userEmail, browser, refreshTokenDataString);
            // userEmail 은 Redis 의 키, browser 는 해쉬키(키 안의 키), 위에서 직렬화한 데이터 = 밸류
            // 따라서 레디스에서 -> HashOperations.get(userEmail, HashKey(==browser)) 하면 저 안의 밸류 가져올 수 있음.
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param userEmail 키로 쓰일 유저이메일
     * @param browser 해쉬키로 쓰일 브라우저 이름(크롬, 사파리, 파이어폭스 ..)
     * @param currentTime 체크하는 시점의 시간. 아규먼트로 "호출" 하실때는 System.currentTimeMillis() 로 호출하시면 됩니다.
     * @return 리프레쉬토큰이 null 이 아니며, currentTime 이 만료 시간인 Duration 보다 크다면 (시간이 지났다면) true 반환합니다.
     */
    public boolean isRefreshTokenExpired(String userEmail, String browser, long currentTime) {
        List<Object> refreshTokenData = getRefreshTokenData(userEmail, browser);
        return refreshTokenData != null && currentTime >= (long) refreshTokenData.get(2);
    }

    /**
     *
     * @param userEmail 키
     * @param browser 해쉬 키
     * @return 유저의 키 안에 있는 해쉬 키의 데이터를 반환합니다. 해쉬 키의 상응 데이터는 List<> 타입입니다.
     */
    public List<Object> getRefreshTokenData(String userEmail, String browser) {
        String refreshTokenDataString = hashOperations.get(userEmail, browser);
        if (refreshTokenDataString != null) {
            try {
                return objectMapper.readValue(refreshTokenDataString, List.class);
                // objectMapper 로
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 
     * @param userEmail 레디스 키
     * @param browser 해쉬 키
     *                해쉬 키를 삭제합니다.
     */
    public void removeRefreshToken(String userEmail, String browser) {
        hashOperations.delete(userEmail, browser);
    }
}

설명

HashOperations 라는 객체를 의존 주입 받아서, 그 매서드를 사용 할 수 있다. (인터페이스이므로)

<Key, Hash Key, Hash Value> 형태로 이루어진 자료구조이며, 매서드는 아래와 같다

HashOperations?

레디스에는 여러가지 자료 구조를 저장 가능하다. 사용 가능한 데이터 타입 블로그 링크

그리고 HashOpertaions 는 Value 에 해쉬 데이터(Key:Value) 를 넣을 수 있게 해준다.

스프링 내부에서 HashOperations(Spring-Data-Redis에 포함되어 있다)는 아래와 같다.

나의 경우에는 위 구조를

<사용자 이메일, 브라우저, List<> 타입의 정보들>로 구성했는데, 여기서 리스트의 크기는 3으로 제한하였으며, 구성 요소는

0번 인덱스 == 브라우저
1번 인덱스 == 리프레쉬 토큰(밸류)
2번 인덱스 == 만료 기한

해쉬 키로 브라우저가 아닌 다른 것 (브라우저 정보라던지, 기기 정보라던지...)이 들어갈 수 있을 수 있다고 생각해서, 위와 같이 작성.

더불어, HashOperations 객체를 Configuration 클래스에서 빈 명시 해줘야 한다.

@Configuration
public class RedisConfig {
...

    @Bean
    public HashOperations<String, String, String> hashOperations(RedisTemplate<String, String> redisTemplate) {
        return redisTemplate.opsForHash();
    }
    ...
    
}

위와 같은 형태. 위에서 명시적으로 사용하므로 DAO는

@Repository
@RequiredArgsConstructor
public class RedisAuthDAO {
    private final RedisTemplate<String, String> redisTemplate;
    private final HashOperations<String, String, String> hashOperations;
    private final ObjectMapper objectMapper;
    }

위와 같이 의존 주입 받아 사용한다.

0개의 댓글