새소식

Programming/Spring

Session에 대해서 - 컨트롤러에서 HttpSession 사용하기

 

 

Spring MVC - HttpSession

 

HttpSession은 서블릿이 제공하는 일종의 쿠키로,

`Cookie : JSESSIONID=3LKSAJFKJ20298349028SD9F0`과 같이 랜덤값으로 설정되며 주로 로그인 처리를 위해 사용한다.

세션에 로그인한 사용자의 정보를 넣어두고 사용하는 것이다.

 

 

컨트롤러에서 세션 사용하기

 

1. 세션에 값 담기

 

컨트롤러의 파라미터로 `HttpServletRequest`를 받아 `request.getSession()`으로 세션을 생성할 수 있다.

@PostMapping("/login")
public String login(@ModelAttribute User user, HttpServletRequest request) {
    (검증 로직 생략)
    HttpSession session = request.getSession();
    session.setAttribute("user", user);
    return "redirect:/";
}

 

  • `request.getSession(true)`
    • 세션이 존재하면 기존 세션 반환
    • 세션이 존재하지 않으면 새로운 세션 생성하여 반환
    • `request.getSession()`과 동일(디폴트 값이 true)
  • `request.getSession(false)`
    • 세션이 존재하면 기존 세션 반환
    • 세션이 존재하지 않으면 null 반환(새로운 세션 생성 X)

 

`HttpSession session = request.getSession();`으로 세션을 생성하였으면

`session.setAttribute(String name, Object value);`로 값을 저장할 수 있다. 

 

 

이후 로그아웃 처리를 하는 경우에는 더이상 세션에 값을 유지할 필요가 없으므로

`session.invalidate();`로 세션을 제거한다.

@PostMapping("/logout")
public String logout(HttpServletRequest request) {
	HttpSession session = request.getSession(false); 
	if (session != null) {
		session.invalidate();
	}
	return "redirect:/";
}

 

이 때 `request.getSession(false)`를 사용했는데

여기서 세션이 새로 만들어져도 의미가 없으므로 생성하지 않도록 했다.

 

참고로 대부분의 사용자는 로그아웃보단 웹을 그냥 종료하기 때문에

`invalidate()`로 세션을 삭제하는 것 뿐만 아니라 세션의 종료시점도 설정해야 한다.

// 글로벌 설정(Spring Boot): application/properties 파일에 추가
// 분 단위로 설정해야 함
server.servlet.session.timeout=60 // 60초

// 특정 세션 단위로 설정: 세션을 설정하는 컨트롤러에서 작성
session.setMaxInactiveInterval(1800); // 1800초

 

마지막 사용 시점(사용자의 최근 요청 시간) 이후로 설정한 시간만큼 계속 생존 시간이 늘어난다.

 

 

2. 세션에 있는 값 가져오기

`session.getAttribute(String name)`으로 넣어놨던 값을 가져올 수 있다.

@GetMapping("/")
public String home(HttpServletRequest request, Model model) {
    
    HttpSession session = request.getSession(false);
    
    // 세션이 없는 경우
    if (session == null) {
         return "home"; //홈으로 이동
    }
    
    User loginUser = session.getAttribute("user");
    // 세션은 있으나 세션에 사용자 데이터가 없는 경우
    if (loginUser == null) {
    	return "home";
    }
    
    // 세션이 있는 경우
    if (loginUser != null) {
        model.addAttribute("user", loginUser); // 세션값을 따로 모델에 담아야 html에서 이용 가능
        return "loginhome"; //로그인한 사용자 홈으로 이동
    }

    return "home";
}

 

 

 

 

HttpSession vs request.getSession()

 

그런데 한가지 의문이 들 수 있다. (내가 들었음)

`request.getSession()`의 반환 타입이 `HttpSession`인데

컨트롤러의 파라미터를 `HttpServletRequest`가 아니라 `HttpSession`을 바로 받아서 사용해도 되지 않을까?

@PostMapping("/login")
public String login(@ModelAttribute User user, HttpSession session) {
	(검증 로직 생략)
	session.setAttribute("user", user);
	return "redirect:/";
}

 

결론적으로 둘 다 사용 가능하다.

 

두가지 방식의 차이점은

`HttpSession`을 사용하면 반드시 세션을 생성하게 되고

`HttpServletRequest`를 사용하면 위에서 보았듯 세션 생성 여부를 선택할 수 있다.

따라서 필요할 때만 세션을 생성하여 사용할 수 있는 것이다.

 

  • `HttpSession`
    • 컨트롤러 호출 시 세션이 존재하면 존재하는 세션 전달
    • 없으면 새로운 세션 생성
  • `request.getSession(Boolean create)`
    • 컨트롤러 호출 시 세션이 존재하면 존재하는 세션 전달
    • 없으면 create 값에 따라 새로 생성 또는 null 반환

 

 

@SessionAttribute

 

스프링은 세션을 더 편리하게 사용할 수 있는 `@SessionAttribute`를 지원한다.

이 애너테이션은 세션을 새로 생성하지는 않지만 등록되어 있는 세션을 찾을 때 유용하게 사용할 수 있다.

 

@GetMapping("/")
public String home(@SessionAttribute(name = "user", required = false) User user, Model model) {
	
    // 세션에 데이터가 없으면 홈으로 이동
    if (user == null) {
    	return "home";
    }
    
    // 세션에 데이터가 있으면 로그인된 사용자 홈으로 이동
    model.addAttribute("user", user);
    return "loginhome";

}

 

세션 존재 여부, 세션의 사용자 데이터 존재 여부를 따로 확인하지 않고 간단하게 사용 가능하다.

 

 

직접 애너테이션 만들어 사용하기

 

`@SessionAttribute`보다 좀 더 간단하게 사용하려면 직접 애너테이션을 만들어 쓰는 방법도 있다.

@GetMapping("/")
public String home(@Login User user, Model model) { // @Login 애너테이션 사용

    if (user == null) {
        return "home";
    }
    
    model.addAttribute("user", user);
    return "loginHome";
}

 

`@Login` 애너테이션이 세션에 있는 로그인 회원 데이터를 찾아주고, 세션에 데이터가 없다면 null을 반환하도록 할 것이다.

 

 

1. 인터페이스 생성

@Target(ElementType.PARAMETER) // 파라미터에만 사용
@Retention(RetentionPolicy.RUNTIME) // 런타임까지 애너테이션 정보가 남아있음
public @interface Login {}

 

 

2. `HandlerMethodArgumentResolver`를 구현한 클래스 생성

public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {
    
    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        boolean hasLoginAnnotation = parameter.hasParameterAnnotation(Login.class); // 파라미터: step1에서 생성한 인터페이스
        boolean hasUserType = User.class.isAssignableFrom(parameter.getParameterType());

        return hasLoginAnnotation && hasUserType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

        HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = request.getSession(false);

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

        return session.getAttribute("user");
    }
}

 

  • `supportsParameter()`: `@Login`이 있으면서 `User` 타입이면 해당 `Resolver`가 실행됨
  • `resolveArgument()`: 컨트롤러 호출 직전에 호출되어 필요한 파라미터 정보를 생성함. 세션의 `User` 객체를 찾아 반환하도록 함

 

3. `WebConfig`에 설정 추가

@Configuration
public class WebConfig implements WebMvcConfigurer {

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

 

`WebMvcConfigurer`를 구현한 클래스인 `WebConfig`를 만들어 `Resolver`를 등록한다.

 

이제 확인해보면 `@Login`이 제대로 동작하는 것을 볼 수 있을 것이다.

 

 

 

 

참고

 

 

Contents

Copied URL!

Liked this Posting!