250x250
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 이진탐색
- 스프링 폼
- 낙관적락
- 스토어드 프로시저
- FetchType
- 연관관계
- 유니크제약조건
- PS
- JPQL
- eager
- 동적sql
- CHECK OPTION
- 지연로딩
- 즉시로딩
- 힙
- execute
- shared lock
- exclusive lock
- 다대다
- 연결리스트
- dfs
- fetch
- BOJ
- 다대일
- 데코레이터
- SQL프로그래밍
- 비관적락
- querydsl
- 일대다
- 백트래킹
Archives
- Today
- Total
흰 스타렉스에서 내가 내리지
Redis 를 사용하여 Refresh Token 구현하기 본문
728x90
Redis 설치와 실행
- redis 설치
$ brew install redis - redis 실행
$ brew services start redis - redis-cli 실행
$ redis-cli
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 를 사용하여 성능상 이점을 가져가는 것이 좋다.
'Spring' 카테고리의 다른 글
프록시 동등성 비교 - equals() 와 hashCode() 오버라이딩 (0) | 2024.04.28 |
---|---|
메서드 호출 결과를 캐시에 저장하는 @Cacheable 과 @CacheEvict (0) | 2024.03.28 |
JPQL 로 ORDER BY RAND() LIMIT N 사용하기 (0) | 2024.03.12 |
쿼리를 5,000번 날리는 API가 있다?😬 쿼리 줄이기 8시간 삽질 후기 (0) | 2024.02.15 |
[JPA] findById() 와 getReferenceById() (2) | 2024.02.09 |