[Spring] 로그인 어노테이션으로 세션 정보가져오기 (Argument Resolver 활용)

로그인 사용자에 대한 기능들을 구현하다보면 로그인 사용자의 Session 정보를 활용할 일이 정말 많다.

그렇다 보니 Session 정보를 가져오는 코드마다 아래의 코드가 반복될 것이다.

SessionUser user = (SessionUser) httpSession.getAttribute("loginUser");

 

이 같은 코드가 반복되는 것을 방지하고자 Spring의 argument resolver를 활용하여

메서드 파라미터의 SessionUser 앞에  @Login 어노테이션을 붙여 세션값을 바로 받아 올 수 있도록 구현할수 있다.

또한 사용자별 세션 정보를 갖고 있으므로, 사용자별로 서로 다른 페이지 내용을 볼 수 있다. ( ex: 마이페이지 )


SessionUser 클래스

세션에 저장할 사용자의 정보를 담을 클래스로 민감한 부분은 제거하고 필요한 부분만을 선언하였다.

(만약 소셜 로그인을 구현한다면 email, picture 필드가 추가로 들어갈수 있다.)

@Getter
public class SessionUser implements Serializable {
    private String name;

    public SessionUser(User user) {
        this.name = user.getName();
    }
}

세션에 저장하기 위해 직렬화를 구현해야 한다.

 


@Login 어노테이션 생성

SessionUser 클래스 앞에 붙여 로그인 여부를 판별할 @Login 어노테이션을 만들어준다.

@Target(ElementType.PARAMETER) // 어노테이션 생성 위치
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 유지 기간
public @interface Login {
}

@Target정의한 어노테이션이 붙을 수 있는 타입을 정의하는 부분이다.

ElementType.PARAMETER 옵션을 넣어 파라미터에 붙을 수 있는 어노테이션으로 정의 하였다.

 

@Retention은 어노테이션의 생명주기를 설정하는 것이다.

RetentionPolicy.RUNTIME 옵션을 넣어 실행될 때까지 어노테이션을 유효하도록 설정하였다.

 

해당 어노테이션은 HandlerMethodArgumentResolver 를 통해 어떠한 기능을 할지 구현할 예정이다.


LoginUserArgumentResolver

HandlerMethodArgumentResolver 인터페이스를 구현하는 구현체로 각각의 오버라이딩 메소드인 supportsParameter와 resolveArgument를 구현해준다.

 

클래스를 통해 @Login 어노테이션이 어떠한 역할을 할지 설정해주는 것이다.

/**
 * 사용자의 세션 정보를 컨트롤러 메서드의 파라미터로 전달하기 위해 사용되는 클래스
 */
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
    /**
     * 컨트롤러 메서드의 특정 파라미터를 지원하는지 판단
     * 파라미터에 @Login 어노테이션이 붙어 있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true를 반환.
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(Login.class) != null;
        boolean isSessionClass = SessionUser.class.equals(parameter.getParameterType());
        return isLoginUserAnnotation && isSessionClass;
    }

    /**
     * 파라미터에 전달할 객체를 생성한다.
     * 여기선 세션에서 객체를 가져온다.
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();

        HttpSession session = request.getSession(false);

        if (session == null) {
            return null;
        }

        return session.getAttribute(LOGIN_USER);
    }
}
 
supportsParameter 

주석의 설명과 같다.

@Login 어노테이션이 붙어있고, 메소드 파라미터가 SessionUser 클래스 타입이라면 true를 반환한다.

 

resolveArgument 

만일 supportsParameter의 리턴값이 false라면, 해당 메소드는 실행되지 않는다.

true 인경우에만 실행되며 HttpSession에서 세션 값이 있을 경우 "LOGIN_USER"로 저장된 객체 반환한다.

(세션 값이 없을 경우 null을 반환하며 비 로그인 사용자인 것을 알 수 있다.)


WebConfig

LoginUserArgumentResolver 클래스가 인식될 수 있도록 다음과 같이 설정클래스에서 추가해줘야한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

오버라이딩한 메서드에 LoginUserArgumentResolver 객체를 추가하면, 스프링 MVC가 HTTP 요청 처리 시 해당 Resolver를 사용하여 매개변수를 해석하게 된다.


@Login 어노테이션을 사용한 로그인 정보 활용 예시

다음은 장바구니컨트롤러에서 로그인 한 사용자인지 비 로그인 사용자인지 판별 하여 view에 data를 넘겨주는 코드이다.

로그인한 사용자의 경우 장바구니에 저장한 상품들을 view에 전달해주고, 비 로그인 사용자의 경우에는 바로 view를 반환한다.

(비 로그인 사용자는 LocalStorage에 담긴 장바구니 정보를 보여주도록 하였다.)

@Controller
@RequiredArgsConstructor
public class CartController {
    private final CartService cartService;

    @GetMapping("/user/cart")
    public String loginUserCart(@Login SessionUser sessionUser, Model model) {
        if (sessionUser != null) {
            List<CartAndImageDto> cartAndImages = cartService.selectCartAndImageByUserId(sessionUser.getUserIdNo());
            model.addAttribute("cartAndImages", cartAndImages);
        }
        return "user/cart";
    }
}

이 처럼 HttpSession에서 일일이 세션정보를 꺼내오는 코드없이 @Login 어노테이션을 붙임으로서 간편하게 로그인 사용자 정보를 가져올 수 있다.