본문 바로가기

스프링

커스텀 ArgumentResolver를 구현하여 세션에 의존적인 파라미터 의존성 제거하기

문제

현재는 세션 로그인 방식을 사용하고 있다. 따라서 로그인된 사용자에 대한 정보를 가져오기 위해서는 세션에 저장된 사용자의 정보를 가져와야 했다.

 

세션에 저장된 사용자의 정보를 가져오기 위해 @SesseionAttribute 어노테이션을 사용하였다.

@LoginCheck(authority = UserAuthority.USER)
@PostMapping
public ResponseEntity<Void> createAuthor(
		@RequestBody @Valid AuthorCreateRequest authorCreateRequest,
		@SessionAttribute(value = UserSessionUtil.LOGIN_MEMBER_ID, required = false) UserAuthDTO userAuth,
		HttpServletRequest httpServletRequest
) {
	Long savedAuthId = authorService.createAuthor(authorCreateRequest, userAuth.loginId());

	return ResponseEntity.created(URI.create(httpServletRequest.getRequestURI() + "/" + savedAuthId)).build();
}

당장은 문제가 없지만, 만약 세션 로그인 방식 대신 JWT나 다른 세션을 사용하지 않는 로그인 방식을 사용한다면 @SessionAttribute가 적용된 모든 컨트롤러 코드를 수정해야 하는 문제가 발생할 수 있다.

 

해결

ArgumentResolver를 커스텀하여 UserAuthDTO가 파라미터로 들어오면 로그인 방식에 따라 다르게 UserAuth를 바인딩하도록 한다.

@Component
public class UserAuthSessionArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(UserAuthDTO.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

        HttpSession session = httpServletRequest.getSession(false);
        if (session == null) {
            throw new BusinessException(ErrorCode.SESSION_EXPIRATION);
        }

        return UserSessionUtil.getLoginUserAuth(session);
    }
}

현재는 세션 로그인 방식을 사용하고 있기 때문에 UserAuthDTO 타입으로 파라미터를 받는다면 세션에 저장되어 있는 UserAuthDTO 객체를 반환하도록 하였다.

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

	private final UserAuthSessionArgumentResolver userAuthSessionArgumentResolver;

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

커스텀한 ArgumentResolver를 등록한다.

@LoginCheck(authority = UserAuthority.USER)
@PostMapping
public ResponseEntity<Void> createAuthor(
		@RequestBody @Valid AuthorCreateRequest authorCreateRequest,
		UserAuthDTO userAuth,
		HttpServletRequest httpServletRequest
) {
	Long savedAuthId = authorService.createAuthor(authorCreateRequest, userAuth.loginId());

	return ResponseEntity.created(URI.create(httpServletRequest.getRequestURI() + "/" + savedAuthId)).build();
}

이제 로그인한 사용자에 대한 정보(UserAuthDTO)를 받을 때 @SessionAttribute를 사용하지 않는다. 즉, 세션에 대한 의존성이 사라지게 되었다.

 

로그인 방식이 JWT 방식으로 바뀐다면

@RequiredArgsConstructor
@Component
public class UserAuthJwtArgumentResolver implements HandlerMethodArgumentResolver {

    private final JwtTokenManager jwtTokenManager;
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.getParameterType().equals(UserAuthDTO.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        HttpServletRequest httpServletRequest = webRequest.getNativeRequest(HttpServletRequest.class);

        try {
            String authorizationHeader = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);
            String token = getJwtTokenByHeader(authorizationHeader);
            return jwtTokenManager.getUserAuthFromToken(token);
        } catch (NullPointerException e) {
            throw new IllegalArgumentException("인증 헤더가 존재하지 않습니다.");
        }
    }

    private String getJwtTokenByHeader(String authorizationHeader) {
        return authorizationHeader.split(" ")[1];
    }
}

위와 같이 ArgumentResolver만 변경해주면 컨트롤러의 파라미터에 대한 코드 변경 없이 사용자 인증 정보를 동일한 코드로 받을 수 있다.