흰 스타렉스에서 내가 내리지

Redis 를 사용하여 Refresh Token 구현하기 본문

Spring

Redis 를 사용하여 Refresh Token 구현하기

주씨. 2024. 3. 28. 09:22
728x90

Redis 설치와 실행

  • redis 설치
    $ brew install redis
  • redis 실행
    $ brew services start redis
  • redis-cli 실행
    $ redis-cli

redis 실행

 

 

 

With Spring Boot

* gradle 의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

 

* application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: 1234

 

 

* Configuration

@Configuration
@EnableCaching
public class RedisConfig {

    @Value("${spring.data.redis.host}")
    private String host;

    @Value("${spring.data.redis.port}")
    private String port;

    @Value("${spring.data.redis.password}")
    private String password;


    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(Integer.parseInt(port));
        redisStandaloneConfiguration.setPassword(password);

        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }


    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        
        return redisTemplate;
    }
}

 

 

* RefreshToken Entity

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
@RedisHash(value = "jwtToken", timeToLive = 60*60*24*3)     // 설정한 값을 Redis 의 key 값 prefix 로 사용한다. ttl 은 3일
public class RefreshToken {

    @Id     // key 값이 되며, jwtToken:{id} 위치에 auto-increment 된다.
    private String id;

    @Indexed        // 이 어노테이션이 있어야, 해당 필드 값으로 데이터를 찾아올 수 있다.
    private String accessToken;

    private String refreshToken;
    
}

 

  • 만료된 access token 으로 refresh token 을 찾아와서 유효성 검사를 할 것이기 때문에, access token 에 @Indexed 어노테이션을 걸어준다.
  • @RedisHash 어노테이션은 Redis Lettuce 를 사용하기 위해 작성해야 한다. 
    • value 는 redis key 값의 prefix로 사용된다. 
    • redis key 는 {value}:{@Id 어노테이션 값} 이 저장된다.

  • timeToLice 는 유효시간이다. "초" 단위이다. 
    • 지정된 시간이 지나면, 데이터는 자동으로 Redis 에서 삭제된다. 
    • 데이터의 유효기간을 관리하는 데 유용하고, 임시 데이터나 캐시 데이터를 다룰 때 자주 사용된다. 

 

 

* Repository

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, String> {

    // @Id 또는 @Indexed 어노테이션을 적용한 프로퍼티들만 CrudRepository 가 제공하는 findBy~ 구문을 사용할 수 있다.
    Optional<RefreshToken> findByAccessToken(String accessToken);
}
  • @Id 또는 @Indexed 어노테이션을 적용한 프로퍼티들만 findBy~~() 메서드를 정의하고 사용할 수 있다.
  • 여기서는 RedisTemplate 방식이 아닌 RedisRepository 방식을 사용했다. 

 

* Controller

@RestController
@RequiredArgsConstructor
public class RedisController {
    private final RefreshTokenService refreshTokenService;

    @PostMapping("/redis")
    public ResponseEntity<?> saveToken(@RequestBody RefreshTokenDto.RefreshTokenRequestDto requestDto){
        refreshTokenService.saveTokenInfo(requestDto);

        return new ResponseEntity<>(HttpStatus.OK);
    }

    @GetMapping("/redis-token")
    public ResponseEntity<?> getToken(@RequestParam("token") String token){
        RefreshTokenDto.RefreshTokenResponseDto tokenInfo = refreshTokenService.getTokenInfo(token);

        return new ResponseEntity<>(tokenInfo, null, HttpStatus.OK);
    }

    @GetMapping("/redis-id")
    public ResponseEntity<?> getTokenById(@RequestParam("id") String id){
        RefreshTokenDto.RefreshTokenResponseDto tokenInfoById = refreshTokenService.getTokenInfoById(id);

        return new ResponseEntity<>(tokenInfoById, null, HttpStatus.OK);
    }

    @DeleteMapping("/redis")
    public ResponseEntity<?> deleteToken(@RequestParam("token") String token){
        refreshTokenService.removeRefreshToken(token);

        return new ResponseEntity<>(HttpStatus.OK);
    }
}

 

  • 저장하고, 조회하고, 삭제하는 API 를 구현했다.

 

* Service

@Service
@RequiredArgsConstructor
public class RefreshTokenService {
    private final RefreshTokenRepository refreshTokenRepository;

    @Transactional
    public void saveTokenInfo(RefreshTokenDto.RefreshTokenRequestDto requestDto){
        refreshTokenRepository.save(new RefreshToken(requestDto.getId(), requestDto.getAccessToken(), requestDto.getRefreshToken()));
    }

    @Transactional(readOnly = true)
    public RefreshTokenDto.RefreshTokenResponseDto getTokenInfo(String accessToken){
        RefreshToken refreshToken = refreshTokenRepository.findByAccessToken(accessToken)
                .orElseThrow(() -> new RuntimeException("없다!"));

        return RefreshTokenDto.RefreshTokenResponseDto.from(refreshToken);
    }

    @Transactional(readOnly = true)
    public RefreshTokenDto.RefreshTokenResponseDto getTokenInfoById(String id){
        RefreshToken refreshToken = refreshTokenRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("없다!"));

        return RefreshTokenDto.RefreshTokenResponseDto.from(refreshToken);
    }

    @Transactional
    public void removeRefreshToken(String accessToken){
        refreshTokenRepository.findByAccessToken(accessToken)
                .ifPresent(refreshTokenRepository::delete);
    }
}

 

 

 

* Refresh Token 을 왜 Redis 에?

1. Key - Value , In-memory DB 로 빠르게 접근 가능 

2. Refresh Token 은 영구적으로 저장되는 데이터가 아니다. 

 

Refresh Token 은 영구적으로 저장될 필요가 없기 때문에, In-Memory DB 를 사용하여 성능상 이점을 가져가는 것이 좋다.