이슈
👉 Spring Boot 프로젝트에서는 application.yml에 다양한 설정과 환경 변수처럼 프로젝트에 사용할 값을 지정할 수 있습니다.
이를 @Value 어노테이션으로 값을 불러와 사용할 수 있습니다.
Auth-Service에서 jwt secret, access-expiration-time, refresh-expiration-time의 값을 yml 파일에서 불러오며 사용하였습니다.
처음에 JWT를 생성하고 검증하는 JwtTokenProvider.class를 생성했을 때, jwt secret, access-expiration-time, refresh-expiration-time 값들은 변경되지 않는 값이고 다른 메서드에서 사용될 수 있도록 private static final로 설정하고 @Value로 값을 불러오도록 구성하였습니다.
하지만 값이 올바르게 설정되지 않았다는 오류로 스프링 부트를 제대로 실행이 되지 않았습니다.
문제
👉 @Value 어노테이션을 통해 application.yml 파일에 정의한 값을 가져오는데, 정적 필드로 설정하여 생기는 오류였습니다.
static 키워드를 사용하면 정적 필드를 사용하는 것을 의미합니다. 정적 필드에 설정하여 사용하려던 JwtTokenProvider와 RedisUtil 클래스에서 문제가 있었습니다.
디버깅을 해보니, 스프링에서는 정적 필드에 @Value로 직접적으로 값을 주입해주고 있지 않아, 값을 올바르게 설정하지 못하여 에러가 발생한 것이었습니다.
오류 메시지와 여러 자료를 찾아가 보며, @Value 어노테이션과 static 키워드를 같이 사용할 수 없음을 배우게 되었습니다.
의존성 주입 메커니즘과의 불일치
Spring의 의존성 주입은 빈의 라이프사이클(생성 → 초기화)을 통해 이루어지는데, static 필드는 클래스가 로드될 때 메모리에 올라가기 때문에 @value와 static이 초기화되는 시점이 달라 생기는 문제였습니다.
해결
👉 @Value & static 같이 사용하지 않기 & JwtTokenProvider에서만 yml 값 조회하기
문제를 해결하기 위해 2가지 부분을 수정하였습니다.
첫 번째로 @Value와 static 키워드를 같이 사용하지 않았습니다.
정적 필드에 값을 저장하지 않고, 변수에 값을 설정하여 처리했습니다.
@Component
@Slf4j
@RequiredArgsConstructor
public class JwtTokenProvider {
@Value("${jwt.secret}")
private String secretKey;
@Value("${jwt.access-expiration-time}")
private long ACCESS_TOKEN_EXPIRATION_TIME;
@Value("${jwt.refresh-expiration-time}")
private long REFRESH_TOKEN_EXPIRATION_TIME;
private final RedisUtils redisUtils;
...
}
두 번째로는 JwtTokenProvider.class와 RedisUtil.class에서 모두 jwt secret, access-expiration-time, refresh-expiration-time 값을 조회하도록 코드를 작성했었습니다.
JwtTokenProvider에서만 값을 조회하도록 하고 Refresh Token을 저장하는 부분에 RedisUtil의 메서드를 호출하며 이때 매개변수로 refresh-expiration-time의 값을 전달해 주는 방식으로 코드를 변경하여 문제를 해결하였습니다.
public String generateRefreshToken(Long memberId) {
Date expiryDate = getExpiryDate(REFRESH_TOKEN_EXPIRATION_TIME);
SecretKey secretKey = getSecretKey();
String refreshToken = Jwts.builder()
.subject("refreshToken")
.expiration(expiryDate)
.signWith(secretKey)
.compact();
try {
redisUtils.setData(memberId.toString(), refreshToken, REFRESH_TOKEN_EXPIRATION_TIME);
} catch (RedisConnectionException | DataAccessException e) {
throw new ApiException(ErrorStatus._REDIS_OPERATION_ERROR);
}
return refreshToken;
}