Spring Boot + Redis + Jwt
기존에 Spring Security + Jwt 를 사용해서 만든 웹서비스에 RefreshToken 을 사용하기 위해 Redis 를 사용해보았다.

로그인에 성공하면 AccessToken, RefreshToken 을 발급한다. 이떼 RefreshToken은 Redis 를 통해 인메모리에 유저 ID 값을 키값으로 잡아 Token 값과 같이 저장하였다.
각 서비스를 이용할때 해당 AccessToken 을 사용해 인증, 인가를 진행한다.
AccessToken 만료 시 RefreshToken 을 체크해서 유효하다면 AccessToken을 발급한다. 허나 RefreshToekn 도 만료라면 SecurityContextHolder에 Authentication 을 set 하지 않고 인증 인가를 허가 하지 않는다.
RefreshToken

Spring Data Redis 에서 Redis 를 사용하는 방법은 크게 2가지 방법이 있다.
RedisTemplate 와 Redis Repository 이다.
필자는 후자인 RedisRepository 를 사용하였다. 두 방식 모두 사용전에 Redis 와연결 설정을 해야하는데 Redis 에서는 기본적으로 localhost 에 연결해주기 때문에 따로 설정없이 진행하였다.
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String>{
}마치 JPA Repository 설정하는것과 유사하다. 이렇게 설정하고 token을 조회할땐 @id 값에 해당하는 값을 통해 JPA 와 동일한 메서드를 통해 가져올수있다. 저장 역시 JPA 와 동일하게 save 메서드를 사용하면 된다.

RefreshToken을 저장하면 해당 내용처럼 저장되어진다.
AccessToken의 유효기간은 짧게 RefreshToken은 다소 길게 잡아서 AccessToken 탈취 위험성을 낮춘다.
또한 RefreshToken 의 긴 유효기간에 대비해서 RefreshToken Rotation 방법을 사용했다.
AccessToken 재 발급시 RefreshToken 도 재발급 하는 방식으로 RefreshToken 의 긴 유효기간에서 발생하는 취약점을 보완하였다.
다만 Logout 시 AccessToken의 만료 기간이 아직 남아있다면 서비스에 접근이 가능하지 않나 라는 의문이 생겼다.
그래서 로그아웃 시 AccessToken의 유효기간이 남아있다면 로그아웃 후 에도 서비스 접근이 가능하다는 점에서 위 같은 BlackList 등록 같은 로직을 사용했다.
그러나tateLess 함을 유지하지 못햇다는 점에서 아쉬움 이 남는다.
// 로그아웃 로직
public void logOut(HttpServletRequest request){
String refreshToken = request.getHeader("refreshToken");
String accessToken = request.getHeader("accessToken");
if (refreshToken != null && validateRefreshToken(refreshToken)){
String idByToken = getIdByToken(refreshToken);
refreshTokenRepository.deleteById(idByToken);
if (validateAccessToken(accessToken)){
BlackListTokenDto blackListTokenDto = BlackListTokenDto.builder()
.token(accessToken)
.memberId(getIdByToken(accessToken))
.build();
BlackListToken blackListToken = blackListTokenDto.toDto(blackListTokenDto);
blackListTokenRepository.save(blackListToken);
}
}
/// 엑세스 토큰 검증 시 blackList 해당 여부 확인 로직 추가
public boolean validateAccessToken(String accessToken) {
if(validateBlackListToken(accessToken)){
log.info("해당 토큰은 유효하지 않습니다.");
return false;
}
try {
SecretKey key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8));
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(accessToken);
return !claims.getBody().getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}정리
RefreshToken을 사용 함으로써 기존 AccessToken 만을 사용하였을때의 토큰 탈취 위험성을 낮출수있었다. 문제가 생기면 RefreshToken 을 삭제 시키면 되기에 Logout 기능도 구현하였다.
요새는 많은 회사들이 클라우드 서비스를 사용하는데 주로 스케일 아웃 방식을 선호한다고 알고있다.
Stateless 한 방식을 사용하는 이유에는 해당 방식이 스케일 아웃 방식에 Fit 한 방식이라는 점을 빼놓을수 없을거같다.
Last updated