일반적으로 프로그래밍에서 같은 코드가 반복되는 것은 개선이 필요한 나쁜 코드에 속한다. 같은 코드를 계속해서 복사 및 붙여넣기로 반복한다면, 이후에 수정이 필요할 때 모든 부분을 하나씩 찾아가며 수정해야 한다. 이러한 경우는 유지 보수성이 떨어질 수밖에 없으며, 혹시나 수정이 반영되지 않은 코드가 있다면 문제가 발생할 수 있다.
그러면 앞서 만든 코드에서 개선이 필요할 만한 부분을 찾아보자. 아마 HomeController에서 세션 값을 가져오는 부분일 것이다.
SessionUser user = (SessionUser) httpSession.getAttribute("user");
해당 코드가 작성된 메서드 외에 다른 컨트롤러와 메서드에서 세션 값이 필요하면, 그때마다 직접 세션에서 값을 가져와야 한다. 이는 결국 같은 코드가 반복되는 것이므로 불필요하다. 따라서 이 부분을 메서드 인자로 세션 값을 바로 받을 수 있도록 변경할 것이다.
config.auth 패키지에 @LoginUser 어노테이션을 생성하자.
package toy.project.bulletin_board.config.auth;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
@Target(ElementType.PARAMETER)PARAMETER로 지정 ⇒ 메서드의 파라미터로 선언된 객체에서만 사용 가능@interface같은 위치에 LoginUserArgumentResolver를 생성하자. 이 클래스는 HandlerMethodArgumentResolver 인터페이스를 구현한 클래스이다.
참고:
HandlerMethodArgumentResolver인터페이스가 지원하는 기능
조건에 맞는 경우, 메서드가 있다면HandlerMethodArgumentResolver의 구현체가 지정한 값으로 해당 메서드의 파라미터로 넘길 수 있다. 자세한 사용법은 직접 만들면서 배워보자.
package toy.project.bulletin_board.config.auth;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import toy.project.bulletin_board.config.auth.dto.SessionUser;
@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
private final HttpSession httpSession;
// 컨트롤러 메서드의 특정 파라미터를 지원하는지 확인
@Override
public boolean supportsParameter(MethodParameter parameter) {
// 파라미터에 @LoginUser 어노테이션이 붙어있으면 true
boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;
// 파라미터 클래스 타입이 SessionUser.class면 true
boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());
return isLoginUserAnnotation && isUserClass;
}
// 파라미터에 전달할 객체를 세션에서 가져오기
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return httpSession.getAttribute("user");
}
}
supportsParameter@LoginUser 어노테이션이 붙어있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true 반환resolveArgument@LoginUser를 사용하기 위한 환경은 구성되었다. 이제 이렇게 생성된 LoginUserArgumentResolver가 스프링에서 인식될 수 있도록 WebMvcConfigurer에 추가하면 된다.
config 패키지에 WebMvcConfig 클래스를 생성하여 아래와 같이 설정을 추가하자.
package toy.project.bulletin_board.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import toy.project.bulletin_board.config.auth.LoginUserArgumentResolver;
import java.util.List;
@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
private final LoginUserArgumentResolver loginUserArgumentResolver;
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(loginUserArgumentResolver); // HandlerMethodArgumentResolver 추가
}
}
HandlerMethodArgumentResolver는 항상 WebMvcConfigurer의 addArgumentResolvers()를 통해 추가해야 한다. 만약 다른 HandlerMethodArgumentResolver가 필요하다면 같은 방식으로 추가하면 된다.
최종적으로 스프링 시큐리티와 관련된 패키지 구조는 아래와 같다.

모든 설정이 끝났으니 처음 언급한 대로 반복되는 부분들을 모두 @LoginUser로 개선하자.
@Controller
@RequiredArgsConstructor
public class HomeController {
private final PostService postService;
// 메인 화면 - 게시판 목록
@GetMapping("/")
public String postList(Pageable pageable, Model model, @LoginUser SessionUser user) {
Page<Post> posts = postService.findAllPosts(pageable);
model.addAttribute("posts", posts);
if (user != null) {
model.addAttribute("userName", user.getName());
}
return "posts/list";
}
}
이제 어느 컨트롤러든지 @LoginUser만 사용하면 세션 정보를 가져올 수 있다.