RedisDAO 로 거의 인증 관련 일을 처리하고 있긴 하지만, 캐싱에서 혹시라도 RedisTemplate 사용할 일이 있을 수 있다고 판단해서, RedisAuthDAO 라는 인증용 DAO를 만들기로 결정했다.
@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;
}
위와 같이 의존 주입 받아 사용한다.