Spring ‐ ArgumentResolver과 Annotation으로 코드 리팩토링하기 - dnwls16071/Backend_Summary GitHub Wiki

❓ 왜 코드를 개선해야만 했나?

  • 에피소드 측 리뷰를 작성하는 과정에서 프론트엔드 팀원 @yujinyny에게 스펙 컨펌을 요청드렸고 이와 관련한 응답을 아래와 같이 받았습니다.

※ 기존 에피소드 리뷰 DTO

{
    "rating" : 5.0,
    "content" : "개꿀잼1",
    "nickname" : "아무개"
}
프론트엔드 曰 : 닉네임을 입력해야 하는 이유가 뭔가요? 백엔드 측에서 요청 헤더 토큰을 보고 유저 정보를 얻어서 저장해야하지 않을까요?
  • 인증된 사용자의 정보는 이미 서버에서 JWT 토큰을 통해 접근할 수 있는 상태였습니다.
  • 프론트엔드에서 불필요하게 닉네임을 다시 전송하는 것은 다음과 같은 잠재적 문제를 초래할 수 있다고 생각했습니다.

※ 문제점

  • 데이터 불일치 : 토큰 사용자 정보와 전송된 닉네임을 고의로 다르게 할 수도 있음
  • 보안 위험 : 악의적인 사용자가 다른 사용자의 닉네임으로 리뷰를 작성할 가능성
  • 불필요한 데이터 전송 : 이미 서버가 가지고 있는 정보를 중복 전송하는 문제

✨그래서 어떻게 개선했나요?

{
    "rating" : 5.0,
    "content" : "개꿀잼1"
}
  • 프론트엔드에서 요청을 보낼 때 백엔드에서 보관하는 JWT 토큰에서 사용자 정보를 추출하여 자동으로 연결하도록 수정합니다.
  • 프론트엔드는 실제로 필요한 리뷰 데이터만을 전송할 수 있게 됩니다.
  • 서버 측에서 안전하고 신뢰할 수 있는 사용자 정보 처리가 가능합니다.

💡 핵심 코드

@Target(ElementType.PARAMETER)        // 파라미터에만 사용
@Retention(RetentionPolicy.RUNTIME)   // 리플렉션 등을 활용할 수 있도록 런타임까지 어노테이션 정보가 남아있음
public @interface LoginUser {
}
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

        // @LoginUser 어노테이션이 파라미터에 있는가?
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		boolean hasParameterAnnotation = parameter.hasParameterAnnotation(LoginUser.class);
		boolean hasLongParameterType = parameter.getParameterType().isAssignableFrom(Long.class);
		return hasLongParameterType & hasParameterAnnotation;
	}

        // 컨트롤러 호출 직전에 필요한 파라미터 정보를 생성한다.
	@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		checkAuthenticated(authentication);
		JwtAuthentication jwtAuthentication = (JwtAuthentication) authentication.getPrincipal();
		return jwtAuthentication.userId();
	}

	private void checkAuthenticated(Authentication authentication) {
		if(Objects.isNull(authentication)) {
			throw new UserException(UNAUTHORIZED);
		}
	}
}
@Configuration
public class WebConfig implements WebMvcConfigurer {

	private final LoginUserArgumentResolver loginUserArgumentResolver;

	public WebConfig(LoginUserArgumentResolver loginUserArgumentResolver) {
		this.loginUserArgumentResolver = loginUserArgumentResolver;
	}

        // ...

	@Override
	public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
		resolvers.add(loginUserArgumentResolver);
	}
}