- Refresh Token with Spring Boot
- Redis 실행
- Redis 관련 설정파일 작성
- Refresh Token 발급하기
도커를 이용해 Redis를 설치해 보자.
docker run -p 6379:6379 -d redis 을 터미널에서 실행한다.
여기서부터는 Spring Boot에서 진행한다.
spring:
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PW}
jwt:
secret-key: ${SECRET_KEY}
expiredMillis: 600
레디스와 jwt 관련 설정을 추가해준다.
@Configuration
@EnableRedisRepositories
public class RedisConfig {
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("spring.data.redis.password")
private String redisPassword;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
config.setPassword(redisPassword);
return new LettuceConnectionFactory(config);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
// 일반적인 key:value의 경우 시리얼라이저
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
// Hash를 사용할 경우 시리얼라이저
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
// 모든 경우
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
RedisRepository를 사용하기 위해 @EnableRedisRepositories 어노테이션을 설정해주고, RedisTemplate을 사용해 직렬화 설정을 추가한다.
@RedisHash("token")
@AllArgsConstructor
@Getter
public class Token {
@Id
private Long id;
private String refreshToken;
@TimeToLive
private Long expiration;
}
레디스에 저장할 refresh token 엔티티를 작성한다. id는 RedisHash와 함께 key 값이 되고 value는 refreshToken 이다. 토큰을 생성할 때 레디스에 저장할 것이고, @TimeToLive 어노테이션을 통해 만료 기간을 정해준다.
@RedisHash(value = "token", timeToLive = 10)와 같이 TTL을 설정해 줄 수도 있다.
@Repository
public interface TokenRepository extends CrudRepository<Token, Long> {
}
Redis에 토큰을 저장하기 위한 Repository이다.
@Component
@RequiredArgsConstructor
public class JwtUtil {
@Value("${jwt.secret-key}")
private String secret;
private int expirationTimeMillis = 864_000_000; // 10일(밀리 초 단위)
@Value("${jwt.refresh-token-expiration-mills}")
private int refreshTokenExpirationMillis;
private String tokenPrefix = "Bearer ";
private ObjectMapper objectMapper = new ObjectMapper();
private final TokenRepository tokenRepository;
// ...
public String createRefreshToken(Long id, Instant issuedAt) {
String refreshToken = JWT.create()
.withClaim("id", id)
.withIssuedAt(issuedAt)
.withExpiresAt(issuedAt.plusMillis(refreshTokenExpirationMillis))
.sign(Algorithm.HMAC512(secret));
Token token = new Token(id, refreshToken);
tokenRepository.save(token);
return refreshToken;
}
JwtUtil에 refresh token을 발급하는 로직을 추가한다.
@RestController
@RequiredArgsConstructor
public class TokenController {
private final JwtUtil jwtUtil;
@GetMapping("/refresh/{id}")
public ResponseEntity<RefreshTokenResponse> getRefresh(@PathVariable("id") Long id) {
String refreshToken = jwtUtil.createRefreshToken(id, Instant.now());
RefreshTokenResponse response = new RefreshTokenResponse(refreshToken);
return ResponseEntity.ok(response);
}
}
컨트롤러에 토큰을 발급하는 API를 추가한다.
아래 dto를 응답 body로 사용한다.
@Getter
@AllArgsConstructor
public class RefreshTokenResponse {
private String refreshToken;
}
이제 /refresh/{id} 경로로 요청을 보내면, refresh token을 발급한다.
위의 코드는 refresh token을 발급하는 예시이고, 실제로는 더 복잡한 과정이 필요하다.
@PostMapping("/refresh")
public ResponseEntity<?> refreshAccessToken(@RequestBody RefreshTokenRequest request) {
String refreshToken = request.getRefreshToken();
// 1. Refresh Token 검증
if (!jwtUtil.validateToken(refreshToken)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid or expired refresh token");
}
// 2. Refresh Token에서 사용자 정보 추출
Long userId = jwtUtil.extractUserId(refreshToken);
// 3. 새로운 Access Token 생성
String newAccessToken = jwtUtil.createAccessToken(userId, Instant.now());
// 4. 필요한 경우 새로운 Refresh Token 생성 (옵션)
String newRefreshToken = jwtUtil.createRefreshToken(userId, Instant.now());
// 5. 응답 반환
RefreshTokenResponse response = new RefreshTokenResponse(newAccessToken, newRefreshToken);
return ResponseEntity.ok(response);
}
이 예시 코드처럼 refresh token으로 access token을 발급하는 로직도 필요할 것이다. 필요한 로직은 Service 단으로 분리해서 사용해야 한다.
이렇게 발급 시 refresh token과 access token을 함께 발급하고, 기존의 refresh token을 비활성화 시키는 로직도 있어야 할 것이다.
프론트엔드에서는 refresh token을 HttpOnly 쿠키에 저장하고, access token이 만료되면 refresh token을 통해 access token을 재발급하도록 하며, 둘 다 만료시 다시 로그인하도록 만든다.
로그아웃 처리 시 refresh token을 redis에서 삭제한다.
이제 refresh token과 RTR 방식을 사용한 토큰 발급을 할 수 있게 되었다.