서블릿 HTTP 세션

서블릿도 우리가 직접 만든 SessionManager와 같은 방식의 HTTP 세션을 제공한다.

Cookie: JSESSIONID=5B78E23B513F50164D6FDD8C97B0AD05

서블릿으로 HttpSession을 생성하면 JSESSIONID라는 이름의 쿠키를 생성하고 추정 불가능한 랜덤 값을 값으로 가진다.

HttpSession

생성 및 조회

// 상수만 가져다 쓰기 때문에 객체를 만들지 못하게 abstract나 interface로 만든다.
public abstract class SessionConst {
    public static final String LOGIN_MEMBER = "loginMember";
}
  • 세션 생성

    • request.getSession(true)

      • 기본값이 true이므로 생략할 수 있다.

      • 세션이 있으면 기존 세션을 반환한다.

      • 없으면 새로 생성해서 반환한다.

    • request.getSession(false)

      • 세션이 있으면 기존 세션을 반환한다.

      • 없으면 생성하지 않고 null을 반환한다.

데이터 저장

session.setAttribute()로 하나의 세션에 여러 값을 저장할 수 있다.

@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {

    @PostMapping("/logout")
    public String logoutV3(HttpServletRequest request) {
        // true면 세션을 만들어버리므로 어차피 없앨 것이기 때문에 false로 한다.
        HttpSession session = request.getSession(false);

        if (session != null) {
            session.invalidate();
        }

        return "redirect:/";
    }
}

JSESSIONID를 확인할 수 있다.

@SessionAttribute

@Slf4j
@Controller
@RequiredArgsConstructor
public class HomeController {

    @GetMapping("/")
    public String homeLoginV3Spring(@SessionAttribute(name = SessionConst.LOGIN_MEMBER, required = false) Member loginMember, Model model) {
        // 세션에 회원 데이터가 없으면 home으로 이동한다.
        if (loginMember == null) {
            return "home";
        }

        // 세션이 유지되면 로그인으로 이동한다.
        model.addAttribute("member", loginMember);
        return "loginHome";
    }
}

getAttribute()를 일일이 하지 않아도 알아서 데이터를 처리해준다.

TrackingModes

http://localhost:8080/;jsessionid=F59911518B921DF62D09F0DF8F83F872

로그인을 처음 시도하면 url에 jsessionid가 붙는다.

  • 웹 브라우저가 쿠키를 지원하지 않을 때 url로 세션을 유지하는 방법

  • url에 값을 계속 포함해서 전달해야 한다.

    • 번거로운 작업이라 거의 사용하지 않는다.

  • 서버 입장에서는 쿠키 지원 여부를 최초에 판단하지 못하기 때문에 쿠키와 url의 jsessionid 모두 전달한다.

server.servlet.session.tracking-modes=cookie

이렇게 설정하면 url에 더 이상 노출되지 않는다.

세션 정보

@Slf4j
@RestController
public class SessionInfoController {

    @GetMapping("/session-info")
    public String sessionInfo(HttpServletRequest request) {
        HttpSession session = request.getSession(false);

        if (session == null) {
            return "세션이 없습니다.";
        }

        // 세션 데이터 출력
        session.getAttributeNames().asIterator()
                .forEachRemaining(name -> log.info("session name={}, value={}", name, session.getAttribute(name)));

        log.info("sessionId={}", session.getId());
        log.info("maxInactiveInterval={}", session.getMaxInactiveInterval());
        log.info("creationTime={}", new Date(session.getCreationTime()));
        log.info("lastAccessedTime={}", new Date(session.getLastAccessedTime()));
        log.info("isNew={}", session.isNew());

        return "세션 출력";
    }
}
login? Member(id=1, loginId=test, name=테스터, password=test!)

session name=loginMember, value=Member(id=1, loginId=test, name=테스터, password=test!)
sessionId=6486F85F291FB8ED8AD5A1F388D780DA
maxInactiveInterval=1800
creationTime=Sun Mar 13 14:44:27 KST 2022
lastAccessedTime=Sun Mar 13 14:44:27 KST 2022
isNew=false
  • sessionId

    • 세션 ID, JSESSIONID의 값

    • ex. 34B14F008AA3527C9F8ED620EFD7A4E1

  • maxInactiveInterval

    • 세션의 유효 시간

    • ex. 1800초, 30분

  • creationTime

    • 세션 생성 일시

  • lastAccessedTime

    • 세션과 연결된 사용자가 최근에 서버에 접근한 시간

    • 클라이언트에서 서버로 sessionId(JSESSIONID)를 요청한 경우 갱신된다.

  • isNew

    • 새로 생성된 세션인지 과거에 만들어져 클라이언트에서 서버로 sessionId(JSESSIONID)를 요청해서 조회된 세션인지 여부

타임아웃

  • 세션은 사용자가 로그아웃을 직접 호출해서 session.invalidate()이 호출되는 경우에 삭제된다.

  • 대부분의 사용자는 로그아웃 대신 웹 브라우저를 종료한다.

    • HTTP는 비연결성이기 때문에 서버는 사용자가 웹 브라우저를 종료했는지 알 수 없다.

    • 따라서 서버에서 언제 세션 데이터를 삭제해야 하는지 판단하기 어렵다.

이 경우 남아있는 세션을 계속 보관하면 문제가 발생할 수 있다.

  • 세션과 관련된 쿠키(JSESSIONID)를 탈취 당하면 시간이 지나도 이 쿠키로 악의적인 요청을 보낼 수 있다.

  • 세션은 기본적으로 메모리에 생성되기 때문에 OOM이 발생할 수도 있다.

    • 10만명이 로그인하면 세션도 10만개 생긴다.

종료 시점

  • 세션 생성 시점을 기준으로 제한 시간을 잡으면 사이트를 이용하면서 30분마다 로그인 해야한다.

    • 제일 최근에 서버에 요청한 시간을 기준으로 시간을 유지하면 된다.

설정

# 60초로 설정한다. 
server.servlet.session.timeout=60
  • 기본값은 30분(1800초)

  • 글로벌로 설정할 때는 60분, 120분 처럼 분 단위로 설정하는 게 좋다.

  • 60초보다 작은 값은 설정할 수 없다.

session.setMaxInactiveInterval(1800);
  • 특정 세션의 시간을 설정한다면 setMaxInactiveInterval()을 이용한다.

타임아웃 발생

  • 해당 세션에 JSESSIONID를 전달하는 HTTP 요청이 있으면 현재 시간으로 다시 초기화 한다.

    • 설정한 시간만큼 세션을 추가로 사용할 수 있다.

  • LastAccessedTime으로부터 timeout 만큼의 시간이 지나면 WAS가 세션을 제거한다.

정리

서블릿의 HttpSession의 타임아웃 기능 덕분에 세션을 안전하고 편리하게 사용할 수 있다.

  • 실무에서는 세션에 최소한의 데이터만 보관해야 한다.

    • 보관한 데이터 용량 * 사용자 수로 세션의 메모리 사용량이 급격하게 늘어나 장애로 번질 수 있다.

  • 세션 시간을 너무 길게 가져가면 메모리 사용이 누적되므로 적당한 시간을 선택한다.

    • 기본이 30분이니 이걸 기준으로 고민한다.

Last updated