// Developer · Debugging Reports

뉴스도 디버깅이 필요해

마주친 오류를 재현하고, 원인을 추적하고, 기록합니다. Java · Spring · DB · 실무 트러블슈팅.

debug — today.log
$ debug --trace "오늘의 뉴스"
> 헤드라인 파싱 중...
> 원인 추적: 근거 누락 · 출처 불명
> 패치 적용: 사실 확인 + 출처 보강
✓ RESOLVED
TOTAL
TODAY
YESTERDAY

Categories

웹개발/Spring

Session 기반 로그인 HttpSession 동작 흐름 정리

자바를잡아2026. 7. 3. 09:00
반응형

세션 기반 로그인이란

웹에서 로그인 상태를 유지하는 가장 전통적인 방식이 세션 기반 로그인이다. HTTP는 요청 하나하나가 독립적이라 서버는 기본적으로 "방금 그 사람"을 기억하지 못한다. 그래서 로그인에 성공하면 서버가 세션이라는 저장 공간을 만들어 사용자 정보를 담아 두고, 이후 요청마다 그 세션을 찾아 "로그인한 사용자"임을 확인한다. Session 기반 로그인 HttpSession 흐름의 핵심은 이 "서버가 기억하는 공간"과 "그 공간을 찾는 열쇠"의 관계다.

그 열쇠가 JSESSIONID다. 서버는 세션을 만들면서 이 ID를 쿠키로 브라우저에 내려 주고, 브라우저는 다음 요청부터 이 쿠키를 자동으로 보낸다. 서버는 받은 JSESSIONID로 해당 세션을 찾아 로그인 정보를 꺼낸다. 공공 SI의 전통적인 웹 로그인 대부분이 이 구조다.

세션 vs 토큰 비교

구분 세션 기반 토큰 기반(JWT 등)
저장 위치 서버(세션) + 쿠키(ID) 클라이언트(토큰)
상태 관리 서버가 상태 보관(stateful) 서버 무상태(stateless)
서버 확장 세션 공유 필요(스티키/스토리지) 비교적 자유로움
무효화 서버에서 즉시 가능 만료 전 무효화 까다로움
주 사용처 전통 웹, 공공 SI API, 모바일, MSA

로그인 요청부터 세션 생성까지

1. 로그인 성공 시 세션에 정보 저장

로그인 컨트롤러는 아이디/비밀번호를 검증한 뒤, 성공하면 HttpSession에 사용자 정보를 담는다. 이 순간 세션이 만들어지고(없었다면), JSESSIONID가 발급된다.

@PostMapping("/login")
public String login(String userId, String password,
                    HttpSession session) {
    MemberVO member = memberService.login(userId, password);
    if (member == null) {
        return "login/fail"; // 인증 실패
    }
    // 인증 성공: 세션에 사용자 정보 저장
    session.setAttribute("loginUser", member);
    return "redirect:/main";
}

비밀번호는 평문 비교가 아니라 해시 기반으로 검증해야 한다(저장도 마찬가지다). 위 예시의 login() 내부에서 안전한 방식으로 비교한다고 보면 된다. 세션에는 비밀번호 같은 민감 정보를 그대로 담지 않고, 식별에 필요한 최소 정보만 담는 것이 좋다.

2. 세션 고정 공격 방지를 위한 세션 재발급

로그인 직후에는 기존 세션 ID를 그대로 쓰지 말고 새로 발급하는 것이 안전하다. 공격자가 미리 만들어 둔 세션 ID를 피해자에게 쓰게 만드는 세션 고정(session fixation) 공격을 막기 위해서다. 로그인 성공 시점에 세션을 새로 시작하면 이전 ID가 무효화된다.

// 로그인 성공 직후 기존 세션 무효화 후 새 세션 사용
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
    oldSession.invalidate(); // 기존 세션 폐기
}
HttpSession newSession = request.getSession(true); // 새 세션
newSession.setAttribute("loginUser", member);

Spring Security를 쓰면 이런 처리가 기본 제공되지만, 직접 구현하는 레거시에서는 이 부분을 놓치기 쉽다. 보안 점검에서 자주 지적되는 항목이므로 기억해 둔다.

다음 요청에서 로그인 상태 확인

로그인 이후 요청에서는 브라우저가 보낸 JSESSIONID로 서버가 세션을 찾고, 거기 담긴 사용자 정보를 꺼내 로그인 여부를 판단한다. 컨트롤러마다 이 검사를 반복하면 지저분하므로, 보통 인터셉터에서 한 번에 처리한다.

// 로그인 여부를 검사하는 인터셉터
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        HttpSession session = request.getSession(false);
        Object loginUser = (session != null) ? session.getAttribute("loginUser") : null;

        if (loginUser == null) {
            response.sendRedirect(request.getContextPath() + "/login");
            return false; // 로그인 페이지로 보내고 요청 중단
        }
        return true; // 로그인 상태면 통과
    }
}

이 인터셉터를 로그인이 필요한 경로에 등록하면, 세션에 사용자 정보가 없는 요청은 로그인 페이지로 돌려보낸다. 로그인 페이지나 정적 리소스는 검사 대상에서 제외(exclude)해야 무한 리다이렉트를 피한다.

세션 만료와 로그아웃

1. 세션 만료 시간 설정

세션은 일정 시간 활동이 없으면 만료된다. 만료 시간은 web.xml에서 분 단위로 설정하거나, 코드에서 초 단위로 지정할 수 있다.

<!-- web.xml : 30분 후 만료 -->
<session-config>
    <session-timeout>30</session-timeout>
</session-config>
// 코드에서 특정 세션 만료 시간 지정 (초 단위)
session.setMaxInactiveInterval(1800); // 30분

만료 시간이 너무 길면 보안에 불리하고, 너무 짧으면 사용자가 자주 튕긴다. 업무 성격에 맞춰 정하되, 공공·금융처럼 민감한 시스템은 짧게 두는 편이다.

2. 로그아웃 처리

로그아웃은 세션을 무효화하면 끝난다. invalidate()를 호출하면 세션에 담긴 정보가 모두 사라진다.

@GetMapping("/logout")
public String logout(HttpSession session) {
    session.invalidate(); // 세션 폐기 → 로그인 정보 제거
    return "redirect:/login";
}

특정 값만 지우려면 removeAttribute("loginUser")를 쓰지만, 로그아웃은 보통 세션 전체를 무효화한다. 무효화 후에는 그 세션의 JSESSIONID로 더 이상 로그인 상태가 인정되지 않는다.

실무에서 자주 하는 실수와 주의점

  • 로그인 시 세션을 재발급하지 않아 세션 고정 공격에 노출되는 경우. 로그인 성공 시 세션을 새로 시작한다.
  • 세션에 비밀번호 등 민감 정보를 그대로 담는 경우. 최소한의 식별 정보만 담는다.
  • 인터셉터에서 로그인 페이지·정적 리소스를 제외하지 않아 무한 리다이렉트가 나는 경우.
  • 서버를 여러 대로 늘렸는데 세션 공유 설정을 안 해, 요청이 다른 서버로 가면 로그아웃되는 경우(스티키 세션 또는 세션 스토리지 필요).
  • JSESSIONID 쿠키에 보안 속성(HttpOnly 등)을 빠뜨리는 경우. 운영에서는 쿠키 보안 속성을 점검한다.

FAQ

Q1. JSESSIONID는 누가 만드나요?

서버(WAS)가 세션을 생성할 때 발급해 쿠키로 내려 준다. 브라우저는 이후 요청마다 이 쿠키를 자동으로 보내고, 서버는 이 ID로 세션을 찾는다.

Q2. 로그인 상태는 어디에 저장되나요?

실제 사용자 정보는 서버의 세션에 있고, 클라이언트(브라우저)에는 세션을 찾는 열쇠인 JSESSIONID만 쿠키로 저장된다.

Q3. 세션 고정 공격은 어떻게 막나요?

로그인 성공 시점에 기존 세션을 무효화하고 새 세션을 발급한다. Spring Security는 이를 기본 제공한다.

Q4. 서버를 늘렸더니 로그인이 풀립니다.

세션은 서버 메모리에 있어, 요청이 다른 서버로 가면 세션을 못 찾는다. 스티키 세션이나 외부 세션 스토리지(예: 공유 저장소)로 세션을 공유해야 한다.

Q5. 세션 방식과 토큰 방식 중 무엇을 써야 하나요?

전통 웹·공공 SI는 세션이 자연스럽고, API·모바일·MSA 환경은 토큰이 유리하다. 환경에 따라 선택한다. 우열 문제가 아니다.

정리

Session 기반 로그인 HttpSession 흐름은 "로그인 성공 → 세션 생성·정보 저장 → JSESSIONID 쿠키 발급 → 이후 요청마다 세션으로 검증 → 만료/로그아웃 시 세션 무효화"로 이어진다. 서버가 상태를 기억하고, 브라우저는 열쇠(JSESSIONID)만 들고 다니는 구조다. 로그인 검증은 인터셉터로 한곳에서 처리하고, 로그인 시 세션 재발급으로 세션 고정 공격을 막으며, 세션에는 최소한의 정보만 담는 것이 안전하다. 서버를 여러 대로 늘릴 때는 세션 공유를 함께 고려해야 한다. 직접 구현하는 레거시라면 보안 처리를 놓치기 쉬우므로, 가능하면 검증된 보안 프레임워크의 도움을 받고 운영 적용 전 점검하는 것이 좋다.

반응형