TECH/웹개발

[Java/Spring] 로그인 구현의 클래식, 세션(Session) 인증 방식 A to Z

런코리치 2026. 3. 16. 08:00

 

웹 서비스를 개발하면서 가장 먼저 마주하는 거대한 벽은 바로 '인증'입니다. 인증의 방식에는 여러 가지가 있습니다. 대표적으로 쿠키, 세션 등입니다. 각 인증 방식별로 특징을 정확히 안다면, 언제 어떤 인증방식을 활용하는 것이 좋은지에 대한 판단이 가능할 것입니다. 오늘은 세션에 대해 알아볼 것입니다.  "어떻게 하면 사용자가 로그인했다는 사실을 서버가 계속 기억하게 할 수 있을까?"라는 질문에 대한 가장 고전적이면서도 강력한 해답은 바로 세션(Session)입니다.

오늘 이 포스팅에서는 세션의 동작 원리부터 실제 구현, 보안 위협 대응, 그리고 실무 설정까지 단 하나도 빠뜨리지 않고 상세히 다루어 보겠습니다.


1. HTTP의 한계와 세션의 탄생

우리가 사용하는 HTTP 프로토콜은 Stateless(비연결성)라는 특징을 가집니다. 서버는 클라이언트가 방금 로그인을 했는지, 아니면 처음 방문했는지 알 길이 없습니다.

이 상태를 유지하기 위해 등장한 개념이 바로 세션(Session)입니다. 사용자의 정보를 서버 측 메모리(또는 DB)에 저장하고, 클라이언트에게는 그 정보에 접근할 수 있는 '열쇠(Session ID)'만 쿠키를 통해 쥐여주는 방식입니다.


2. 세션의 동작 원리: 6단계의 여정

  1. 인증 요청: 사용자가 아이디와 비밀번호를 입력해 서버로 전송합니다.
  2. 검증 및 생성: 서버는 DB를 확인하여 유효한 사용자인지 판단한 후, 서버 메모리에 고유한 Session ID를 생성하고 필요한 정보(User ID, 권한 등)를 저장합니다.
  3. 응답 헤더 설정: 서버는 응답 메시지의 Set-Cookie 헤더에 Session ID를 담아 보냅니다. (예: Set-Cookie: JSESSIONID=ABC12345; HttpOnly)
  4. 쿠키 저장: 브라우저는 받은 Session ID를 쿠키 저장소에 안전하게 보관합니다.
  5. ID 전달: 이후 클라이언트가 서버에 요청을 보낼 때마다 브라우저는 쿠키 저장소에서 Session ID를 꺼내 HTTP 요청 헤더에 자동으로 동봉합니다.
  6. 상태 확인: 서버는 전달받은 ID를 자신의 세션 저장소에서 찾아 일치하는 정보를 확인하고 응답합니다.

3. Spring Boot 실전 구현 가이드

스프링 부트에서는 HttpSession 인터페이스를 통해 복잡한 로직 없이 세션을 제어할 수 있습니다.

✅ 로그인 컨트롤러 (예시: LoginController.java)

Java
@PostMapping("/login")
public String login(@Valid @RequestBody LoginDto loginDto, HttpServletRequest request) {
    // 1. 서비스 레이어에서 사용자 검증
    User loginUser = loginService.login(loginDto.getLoginId(), loginDto.getPassword());

    if (loginUser == null) {
        return "loginFail"; // 로그인 실패 시 처리
    }

    // 2. 세션이 있으면 있는 세션 반환, 없으면 신규 세션 생성
    // getSession(true)가 기본값이며, false로 설정하면 세션이 없을 때 null을 반환합니다.
    HttpSession session = request.getSession();

    // 3. 세션에 로그인 회원 정보 보관 (객체 자체를 담을 수 있습니다)
    session.setAttribute("LOGIN_MEMBER", loginUser);

    return "redirect:/";
}

✅ 로그인 체크 인터셉터 (예시: LoginCheckInterceptor.java)

모든 컨트롤러에서 세션을 체크하는 중복 코드를 방지하기 위해 인터셉터(Interceptor) 활용은 필수입니다.

Java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    HttpSession session = request.getSession(false);

    if (session == null || session.getAttribute("LOGIN_MEMBER") == null) {
        // 로그인되지 않은 사용자라면 로그인 페이지로 리다이렉트
        response.sendRedirect("/login?redirectURL=" + request.getRequestURI());
        return false;
    }
    return true;
}

4. 실무에서 반드시 챙겨야 할 세션 설정

세션은 서버 자원을 사용하기 때문에 관리가 소홀하면 메모리 부족 현상을 겪거나 보안 사고가 발생할 수 있습니다.

① 세션 타임아웃 설정 (application.yml)

사용자가 로그아웃을 명시적으로 누르지 않아도 일정 시간이 지나면 세션을 자동으로 만료시켜야 합니다.

YAML
server:
  servlet:
    session:
      timeout: 30m # 30분 동안 활동이 없으면 세션 만료
      cookie:
        http-only: true # 자바스크립트로 쿠키 접근 불가 (XSS 방지)
        secure: true    # HTTPS 통신에서만 쿠키 전송

② 세션 고정 공격(Session Fixation) 방지

공격자가 미리 생성한 Session ID를 사용자에게 심어놓고 사용자가 로그인하면 그 세션을 가로채는 방식입니다. 스프링 시큐리티를 사용하면 로그인 시마다 세션 ID를 새로 발급하도록 기본 설정되어 있습니다.


5. 세션 방식의 명확한 장단점 비교

구분 장점 단점
보안성 실제 데이터가 서버에 있어 탈취 위험이 적음 Session ID 자체가 탈취되면 방어하기 어려움
제어권 서버에서 특정 세션을 즉시 만료시킬 수 있음 서버 메모리를 직접 사용하므로 동시 접속자가 많으면 부하 발생
구현 구현이 매우 쉽고 직관적임 서버 확장성(Scalability) 문제가 치명적임

 

6. [심화] 분산 환경에서의 세션 불일치 문제

만약 서버가 1대가 아니라 3대로 운영되는 로드밸런싱 환경이라면 어떻게 될까요?

  • 사용자가 서버 1에서 로그인하여 세션을 만들었습니다.
  • 다음 요청이 서버 2로 전달됩니다.
  • 서버 2에는 해당 사용자의 세션 정보가 없어 로그인이 풀려버립니다.

이 문제를 해결하는 3가지 실무 전략:

  1. Sticky Session: 클라이언트가 처음 접속한 서버로만 계속 연결되도록 고정합니다. (특정 서버 부하 집중 위험)
  2. Session Clustering: 모든 서버의 세션 저장소를 실시간으로 동기화합니다. (데이터 전송 부하 발생)
  3. Redis Session Storage (강력 추천): 별도의 외부 공용 저장소(Redis)를 두어 모든 서버가 이곳에서 세션을 읽고 씁니다. 현재 가장 많이 쓰이는 방식입니다.

🏁 마치며: 세션은 여전히 훌륭한 도구입니다.

많은 현대적인 서비스가 JWT로 넘어가고 있지만, 서버 측에서 사용자의 상태를 완전히 제어해야 하거나(예: 중복 로그인 방지, 강제 로그아웃 기능 등), 보안이 극도로 중요한 사내 시스템에서는 여전히 세션이 최고의 선택입니다. 대개 프로젝트를 진행하다 보면 로그인 기능은 당연 갖춰져 있을 것이며, 주로 세션을 활용한다는 것을 알 수 있습니다. 따라서 세션에 대해 그 특징을 정확히 알고 활용한다면 어느 프로젝트에서 개발을 하더라도 정확하게 구현할 수 있을 것입니다.

단순히 남들이 쓰니 사용하는 것이 아닌, 여러분 서비스의 규모와 보안 요구사항에 맞춰 가장 활용에 알맞은 인증 방식을 선택하고, 그것이 만약 세션이라면, 세션을 선택하 훨씬 견고한 시스템을 구축하실 수 있을 것입니다. 세션의 특징을 잘 파악하고, 알맞은 상황에 활용할 수 있도록 그 특징을 잘 알고 활용하면 좋겠습니다. 감사합니다.