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);
}
}