문제
현재는 세션 로그인 방식을 사용하고 있다. 따라서 로그인된 사용자에 대한 정보를 가져오기 위해서는 세션에 저장된 사용자의 정보를 가져와야 했다.
세션에 저장된 사용자의 정보를 가져오기 위해 @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만 변경해주면 컨트롤러의 파라미터에 대한 코드 변경 없이 사용자 인증 정보를 동일한 코드로 받을 수 있다.