Refresh Token은 Access Token을 재발급하기 위한 용도
Refresh Token을 쿠키에 저장하면 오히려 보안성만 떨어뜨리는 행위가 됩니다. 쿠키는 CSRF 공격에 취약하다는 점을 가지고 있어 좋지 않은 방법이라고 결론을 내렸습니다. 마찬가지로 Refresh Token을 세션 스토리지에 저장하는 것도 XSS 공격의 취약성을 가지고 있습니다. 따라서 Refresh Token을 Redis에 저장하는 방식을 채택했습니다.
출처 : https://byungil.tistory.com/309
Key - Value 방식, 인메모리 DB 방식으로 빠르게 접근할 수 있음.@Builder
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@RedisHash(value = "refreshToken", timeToLive = 14440) // 4 hours
public class RefreshToken {
@Id
private String id;
String token;
}
@Id 어노테이션
opg.springframework.data.annotation.Id 를 import해야 함.@RedisHash 어노테이션
Redis Lettuce를 사용하기 위해 작성해야 됨value는 redis key 값으로 사용됨. redis 저장소의 key로는 {value}:{@Id 어노테이션을 붙여준 값}이 저장
public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {
}
Redis Template 방식이 아닌 Redis Repository 방식을 사용, JpaRepository가 아닌 CrudRepository를 상속받아야 함
Redis는 전통적인 관계형 데이터베이스(RDBMS)가 아니라, 인메모리 데이터 저장소입니다. Redis는 주로 빠른 읽기/쓰기를 위해 사용되며, 관계형 데이터베이스에서 제공하는 고급 쿼리 기능, 페이징, 정렬 등의 기능은 제공하지 않습니다.
JpaRepository는 JPA (Java Persistence API) 기반의 리포지토리로, 관계형 데이터베이스를 대상으로 하는 다양한 기능을 제공합니다. 그러나 Redis는 JPA와 관련이 없기 때문에,JpaRepository에서 제공하는 기능이 Redis와는 맞지 않습니다.
-chatGPT-
@Service
@RequiredArgsConstructor
public class RefreshTokenService {
private final RefreshTokenRepository refreshTokenRepository;
private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;
@Transactional
public void saveRefreshToken(String refreshToken) {
RefreshToken token = RefreshToken.builder().id(jwtTokenProvider.getMemberId(refreshToken)).token(refreshToken).build();
refreshTokenRepository.save(token);
}
@Transactional
public AuthToken reissueToken(String refreshToken) {
jwtTokenProvider.validateToken(refreshToken); // refresh token 유효성 검사
RefreshToken oldRefreshToken = refreshTokenRepository.findById(jwtTokenProvider.getMemberId(refreshToken)).orElseThrow(() -> new GlobalException(GlobalErrorCode.AUTH_INVALID_REFRESH_TOKEN));
if (!oldRefreshToken.getToken().equals(refreshToken)) { // 저장된 refresh token과 요청된 refresh token이 다를 경우
throw new GlobalException(GlobalErrorCode.AUTH_INVALID_REFRESH_TOKEN);
}
String newAccessToken = jwtTokenProvider.generateAccessToken(oldRefreshToken.getId());
String newRefreshToken = jwtTokenProvider.generateRefreshToken(oldRefreshToken.getId());
saveRefreshToken(newRefreshToken);
return AuthResponseDTO.AuthToken.builder().accessToken(newAccessToken).refreshToken(newRefreshToken).build();
}
}
RefreshToken을 Redis에 저장하는 메소드와 RefreshToken을 통해 AccessToken을 재발행해주는 메소드
@Override
public AuthToken reissueToken(String refreshToken) {
return refreshTokenService.reissueToken(refreshToken);
}
AuthServiceImpl 클래스에 위 메소드를 추가
@Operation(summary = "토큰 재발급", description = "access 토큰이 만료된 경우 refresh 토큰을 통해 토큰을 재발급 합니다.")
@PostMapping("/kakao/reissue")
public ApiResponse<AuthResponseDTO.AuthToken> reissueToken(@GetToken String refreshToken) {
return ApiResponse.onSuccess("토큰 재발급 성공", authService.reissueToken(refreshToken));
}
AuthController의 /kakao/reissue API