Welcome! Everything is fine.

[Spring] Spring에서 세션을 다루는 3가지 방법, 뭐가 제일 좋을까? 본문

TIL

[Spring] Spring에서 세션을 다루는 3가지 방법, 뭐가 제일 좋을까?

개발곰발 2025. 2. 22. 14:00
728x90

튜터님의 해설 강의를 듣고 일정 관리 앱 과제를 리뷰하다가 알게 된 내용을 간단하게 포스팅 해본다. 이번 일정 관리 앱 과제에서 튜터님의 코드와 비교했을때, 가장 큰 차이 중 하나는 세션을 사용하는 방식이었다. 나는 HttpSession을 직접 가져와 사용하는 방식을 사용했다. 반면 튜터님은 @SessionAttribute 어노테이션을 사용하여 세션 속성을 자동 주입하는 방식을 사용하셨고, 그 방법이 더 쉽게 세션을 사용할 수 있는 것 같다.

📌 세션을 사용하는 방식

HttpSession 사용하기

내가 작성한 댓글 생성 기능 부분이다. HttpServletRequest에서 HttpSession을 가져와 세션키를 사용하고 있다. 이 방법은 Servlet API에서 제공하는 기본적인 세션 관리 방식으로, 직접 HttpSession 객체를 사용하여 값을 저장하고 가져온다.

@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
    @PathVariable Long scheduleId,
    @Valid @RequestBody CommentRequestDto dto,
    HttpServletRequest request
) {
    HttpSession session = request.getSession(false);
    String sessionKey = (String) session.getAttribute("sessionKey");

    CommentResponseDto commentResponseDto = commentService.save(scheduleId, sessionKey, dto);
    return new ResponseEntity<>(commentResponseDto, HttpStatus.CREATED);
}

 

이 경우 한 눈에 보기에도 코드가 길어 간결해보이지 않는다는 단점이 있고, 만약 세션이 없을 경우 NullpointerException이 발생할 가능성이 있다. 또한 session.getAttribute()를 호출할 때 형 변환이 필요하다.

 

실제로 코드를 짜면서 아래 코드가 굉장히 많이 반복되어서 찝찝했는데 시간이 없어서 그냥 제출했다. 리뷰를 하면서 해당 코드가 있는 부분을 고치는데 너무 많아서 힘들었다.

HttpSession session = request.getSession(false);
String sessionKey = (String) session.getAttribute("sessionKey");

@SessionAttribute 사용하기

위 코드는 @SesstionAttribute 어노테이션을 사용해 더 간결하게 바꿀 수 있다. @SessionAttribute는 특정 값을 컨트롤러 메서드에서 세션 속성으로 직접 매핑한다. 자동으로 세션값을 주입해 주기 때문에 불필요한 코드가 사라진다.

@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
    @SessionAttribute(name = "LOGIN_USER") Long userId,
    @PathVariable Long scheduleId,
    @RequestBody CommentSaveRequestDto dto
) {
    return ResponseEntity.ok(commentService.save(userId, scheduleId, dto));
}

 

이 경우에는 첫 번째 방법보다 코드도 짧고 유지보수하기 쉽다. 세션을 가져오고, 속성을 읽은 후 형 변환까지 해야하는 과정 없이 바로 세션값을 사용할 수 있다.

HandlerMethodArgumentResolver 사용하기

세션을 사용하는 방법이 더 있는지 찾아보다가 발견한 방식이다. HandlerMethodArgumentResolver를 구현하여, 특정 세션 값을 자동으로 컨트롤러 매개변수로 주입하는 방식이다. 컨트롤러에서 매번 @SessionAttribute나 HttpSession을 호출할 필요 없이, 자동으로 특정 객체를 주입받을 수 있도록 설정할 수 있다. 세션을 가져오는 로직을 전역적으로 한 곳에서 관리할 수 있기 때문에 더 깔끔하고 유지보수가 쉽다. 사용하는 방법은 다음과 같다.

 

1) @LoginUser 어노테이션 생성
먼저 LoginUser라는 커스텀 어노테이션을 만든다.

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
  • @Target(ElementType.PARAMETER) : 메서드의 파라미터에만 사용할 수 있도록 제한
  • @Retention(RetentionPolicy.RUNTIME) : 런타임에도 어노테이션이 유지되어 ArgumentResolver에서 인식할 수 있도록 설정
    • RetentionPolicy.SOURCE: 소스 코드에서만 유지
    • RetentionPolicy.CLASS: 클래스 파일에만 유지
    • RetentionPolicy.RUNTIME: 런타임 시에도 유지

2) HandlerMethodArgumentResolver 구현
@LoginUser를 사용하면 자동으로 세션에서 값을 가져오도록 HandlerMethodArgumentResolver를 구현한다.

@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // @LoginUser 애노테이션이 있고, Long 타입일 때만 적용
        return parameter.getParameterAnnotation(LoginUser.class) != null
                && parameter.getParameterType().equals(Long.class);
    }

    @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) {
            throw new RuntimeException("세션이 존재하지 않습니다.");
        }

        return userId;
    }
}

 

3) WebConfig에 등록
addArgumentResolvers()에서 위에서 만든 LoginUserArgumentResolver를 SPring의 ArgumentResolver 목록에 추가한다.

@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final LoginUserArgumentResolver loginUserArgumentResolver;

    public WebConfig(LoginUserArgumentResolver loginUserArgumentResolver) {
        this.loginUserArgumentResolver = loginUserArgumentResolver;
    }

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

 

이렇게 하면 컨트롤러에서 @LoginUser 어노테이션을 사용할 수 있다.

 

4) 컨트롤러에서 @LoginUser 사용
이제 다음과 같이 @LoginUser만 붙이면 세션값을 가져올 수 있다. 아마 제일 간결한 방식이지 않을까 싶다.

@PostMapping("/schedules/{scheduleId}/comments")
public ResponseEntity<CommentResponseDto> save(
        @LoginUser Long userId,  // 세션에서 자동으로 userId를 주입
        @PathVariable Long scheduleId,
        @RequestBody CommentSaveRequestDto dto
) {
    return ResponseEntity.ok(commentService.save(userId, scheduleId, dto));
}

 

그 외...

  • Spring Security : 세션 대신 SecurityContext에 사용자 정보 저장 가능
  • Spring Session : 서버 메모리가 아닌 Redis, JDBC, Hazelcast 같은 저장소에 세션 저장 가능
  • 실무에서는 Spring Session + Redis를 많이 사용한다고 한다.(GPT가 한 말이라 100% 신뢰할 순 없지만...)

실무에서 세션을 사용하는 방식은 나중에 더 많은 것을 배우고 나서 다시 공부해보면 좋을 것 같다.