순간을 성실히, 화려함보단 꾸준함을

@Value 어노테이션이 자꾸 null 이 나와요!!!! 본문

나의 개발 메모장

@Value 어노테이션이 자꾸 null 이 나와요!!!!

폭발토끼 2023. 3. 26. 20:58

안녕하세요!!!
벌써 글또 4번째 글을 작성하게 되었네요 ㅎㅎ

이번에 작성할 내용은 특별한 내용은 아니고 삽질을 한 경험을 바탕으로 새롭게 얻은 지식을 정리하는 글을 작성하려고 합니다.
바로 @Value 어노테이션을 사용하여 property 값을 주입받는 과정에서 생긴 일입니다.

먼저 @Value 어노테이션은 언제 사용할까요????

Typically used for expression-driven or property-driven dependency injection.
식 또는 속성기반 표현식의 주입성을 받기 위해 사용됩니다.

즉, 스프링 빈들에 정의되어있는 필드에 값을 주입하기 위해서 사용되는 어노테이션 입니다.

public class Test{

    @Value("${user.name}")
    private String username;
}

대강 이런식으로 사용할 수 있습니다.
더 자세한 내용은 https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Value.html 공식문서를 참조하세요!

자 그럼 이제 제가 마주한 문제에 대해서 설명드리도록 하겠습니다.

현재 진행하고 있는 프로젝트에서 ArgumentResolver 를 이용하여 인증이 필요한 서비스들에 대해서 '인증절차' 를 거치도록 구조를 짜놨습니다.

  1. 로그인 시도
  2. 로그인 성공 후 access token 부여
  3. 클라이언트에서 인증이 필요한 서비스에 접근 시도
  4. ArgumentResolver 에서 access token 검증

이런 플로우로 흘러들어가게 됩니다.

이때, access token 을 생성할 때 고유한 secret key 를 사용하여 token 을 생성하게 됩니다.(이 부분은 jwt token : HS256 알고리즘에 관해서 검색해보세요)

그래서 HS256 암호화 알고리즘으로 생성된 secret key 를 어디선가 고유값으로 저장을 해놔야 할텐데 이를 전 property 파일에 저장을 해놓은 것 입니다.(application-secret.yml 에 저장해 놓았습니다)

spring:
# secret properties
  profiles:
    include: secret

application.yml 파일입니다.

jwt:
  secret: 시크릿 키 값

application-secret.yml 파일입니다.

스프링 프로파일을 이용하여 프로퍼티 파일을 구분지어놨습니다. 스프링 프로파일에 대해서 궁금하신 분들은 아래 공식문서를 참고하세요.
https://docs.spring.io/spring-boot/docs/1.2.0.M1/reference/html/boot-features-profiles.html

public class AuthController {

    private final AuthService authService;

    @Value("${jwt.secret}")
    private String KEY;

    ~~
}

이렇게 controller 에 @Value 어노테이션을 사용하여 KEY 라는 변수에 property 에 정의한 값을 주입받게 해주었습니다.

ArgumentResolver 도 마찬가지입니다.

@Component
@RequiredArgsConstructor
public class AuthResolver implements HandlerMethodArgumentResolver {

    private final SessionJpaRepository sessionJpaRepository;

    /**
     * TODO:jwt.secret 이 null 인 문제...
     * why???
     */
    @Value("${jwt.secret}")
    private String KEY;

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String jws = webRequest.getHeader("Authorization");   //header 에서 token을 꺼내는 방법

        if(jws == null || jws.equals("")){
            log.error("servletRequest null");
            throw new Unauthorized();
        }
        byte[] decodedKey = Base64.getDecoder().decode(KEY);
        try {
            Jws<Claims> claims = Jwts.parserBuilder()
                    .setSigningKey(decodedKey)
                    .build()
                    .parseClaimsJws(jws);

            Long userId = claims.getBody().get("id",Long.class);
            String email = claims.getBody().get("email",String.class);
            String nickname = claims.getBody().get("nickname",String.class);

            return UserSession.builder()
                    .id(userId)
                    .email(email)
                    .nickname(nickname)
                    .build();
        } catch (JwtException e) {
            throw new Unauthorized();
        }
    }
}

이 상태에서 로그인 http 요청부터 보내보았습니다.
아무런 문제없이 성공하고 token 값을 리턴 받았습니다.

그리고 ArgumentResolver 에 디버깅 포인터를 걸은 후 인증절차를 거쳐야 할 API 에 요청을 보내보도록 하겠습니다.

띠용?!! KEY 값이 null 인걸 확인할 수 있습니다....대체 왜그런걸까요???

혹시나 ArgumentResolver 를 Bean 으로 등록을 안해놓은 건지 해서 체크도 해보았지만 @Component 어노테이션도 잘 붙여주었고 만약 Bean 등록을 하지 않았더라면 프로젝트를 실행했을 때 컴파일 에러가 발생해야 되는 것이 맞습니다.

답은 바로!!
ArgumentResolver 를 등록하는 과정에 있어서 문제가 있었던 것 이었습니다

현재 제가 작성한 ArgumentResolver 는 HandlerMethodArgumentResolver 라는 인터페이스를 구현한 커스텀 클래스입니다. 따라서 WebMvcConfigurer 를 implements 하는 WebConfig class 에 따로 등록을 해주고 있는데 이때 new ArgumentResolver 라고 새로운 인스턴스를 등록해주었던 것이 원인이었습니다.

그러면 대체 왜 새로운 인스턴스를 등록한 것이 문제였을까요??
생각을 해보면 @Value 어노테이션을 사용하여 값을 주입받을때 반드시 Bean 으로 등록되어있는 객체여야 합니다.
즉, ArgumentResolver를 스프링에게 알려주어 등록을 하려고 할때도 Bean 으로 등록되어있는 ArgumentResolver 여야지만 @Value 어노테이션이 의도대로 동작을 할 수 있다는 것이죠.

그런데 저는 현재 new 연산자를 사용하여 새로운 인스턴스를 생성하여 등록을 해주고 있으니 Bean 객체 아니어서 값을 주입을 받을 수 없는 것입니다.

그럼 어떻게 해결을 해줄 수 있을까요???
간단합니다. ArgumentResolver를 등록할때 Bean 으로 설정한 객체를 등록하면 됩니다.

이렇게 의존성을 주입받은 객체를 등록해주면 됩니다.

더이상 @Value 어노테이션으로 값을 주입한 KEY 값이 null 이 아닌것을 확인할 수 있습니다.

잘못된 내용이 있거나 피드백 해주실 내용이 있으시면 언제라도 말씀해주시면 감사하겠습니다.

출처 : https://wildeveloperetrain.tistory.com/143#comment17192142