웹 서비스를 개발하면서 가장 먼저 마주하는 거대한 벽은 바로 '인증'입니다. 인증의 방식에는 여러 가지가 있습니다. 대표적으로 쿠키, 세션 등입니다. 각 인증 방식별로 특징을 정확히 안다면, 언제 어떤 인증방식을 활용하는 것이 좋은지에 대한 판단이 가능할 것입니다. 오늘은 세션에 대해 알아볼 것입니다. "어떻게 하면 사용자가 로그인했다는 사실을 서버가 계속 기억하게 할 수 있을까?"라는 질문에 대한 가장 고전적이면서도 강력한 해답은 바로 세션(Session)입니다.
오늘 이 포스팅에서는 세션의 동작 원리부터 실제 구현, 보안 위협 대응, 그리고 실무 설정까지 단 하나도 빠뜨리지 않고 상세히 다루어 보겠습니다.
1. HTTP의 한계와 세션의 탄생
우리가 사용하는 HTTP 프로토콜은 Stateless(비연결성)라는 특징을 가집니다. 서버는 클라이언트가 방금 로그인을 했는지, 아니면 처음 방문했는지 알 길이 없습니다.
이 상태를 유지하기 위해 등장한 개념이 바로 세션(Session)입니다. 사용자의 정보를 서버 측 메모리(또는 DB)에 저장하고, 클라이언트에게는 그 정보에 접근할 수 있는 '열쇠(Session ID)'만 쿠키를 통해 쥐여주는 방식입니다.
2. 세션의 동작 원리: 6단계의 여정
- 인증 요청: 사용자가 아이디와 비밀번호를 입력해 서버로 전송합니다.
- 검증 및 생성: 서버는 DB를 확인하여 유효한 사용자인지 판단한 후, 서버 메모리에 고유한 Session ID를 생성하고 필요한 정보(User ID, 권한 등)를 저장합니다.
- 응답 헤더 설정: 서버는 응답 메시지의 Set-Cookie 헤더에 Session ID를 담아 보냅니다. (예: Set-Cookie: JSESSIONID=ABC12345; HttpOnly)
- 쿠키 저장: 브라우저는 받은 Session ID를 쿠키 저장소에 안전하게 보관합니다.
- ID 전달: 이후 클라이언트가 서버에 요청을 보낼 때마다 브라우저는 쿠키 저장소에서 Session ID를 꺼내 HTTP 요청 헤더에 자동으로 동봉합니다.
- 상태 확인: 서버는 전달받은 ID를 자신의 세션 저장소에서 찾아 일치하는 정보를 확인하고 응답합니다.
3. Spring Boot 실전 구현 가이드
스프링 부트에서는 HttpSession 인터페이스를 통해 복잡한 로직 없이 세션을 제어할 수 있습니다.
✅ 로그인 컨트롤러 (예시: LoginController.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) 활용은 필수입니다.
@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)
사용자가 로그아웃을 명시적으로 누르지 않아도 일정 시간이 지나면 세션을 자동으로 만료시켜야 합니다.
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가지 실무 전략:
- Sticky Session: 클라이언트가 처음 접속한 서버로만 계속 연결되도록 고정합니다. (특정 서버 부하 집중 위험)
- Session Clustering: 모든 서버의 세션 저장소를 실시간으로 동기화합니다. (데이터 전송 부하 발생)
- Redis Session Storage (강력 추천): 별도의 외부 공용 저장소(Redis)를 두어 모든 서버가 이곳에서 세션을 읽고 씁니다. 현재 가장 많이 쓰이는 방식입니다.
🏁 마치며: 세션은 여전히 훌륭한 도구입니다.
많은 현대적인 서비스가 JWT로 넘어가고 있지만, 서버 측에서 사용자의 상태를 완전히 제어해야 하거나(예: 중복 로그인 방지, 강제 로그아웃 기능 등), 보안이 극도로 중요한 사내 시스템에서는 여전히 세션이 최고의 선택입니다. 대개 프로젝트를 진행하다 보면 로그인 기능은 당연 갖춰져 있을 것이며, 주로 세션을 활용한다는 것을 알 수 있습니다. 따라서 세션에 대해 그 특징을 정확히 알고 활용한다면 어느 프로젝트에서 개발을 하더라도 정확하게 구현할 수 있을 것입니다.
단순히 남들이 쓰니 사용하는 것이 아닌, 여러분 서비스의 규모와 보안 요구사항에 맞춰 가장 활용에 알맞은 인증 방식을 선택하고, 그것이 만약 세션이라면, 세션을 선택하 훨씬 견고한 시스템을 구축하실 수 있을 것입니다. 세션의 특징을 잘 파악하고, 알맞은 상황에 활용할 수 있도록 그 특징을 잘 알고 활용하면 좋겠습니다. 감사합니다.
'TECH > 웹개발' 카테고리의 다른 글
| [Security] API 보안의 핵심: Session vs JWT 완벽 비교 가이드 (1) | 2026.03.18 |
|---|---|
| [Java/Spring] 현대 인증의 표준, JWT(JSON Web Token) 완벽 가이드 (0) | 2026.03.17 |
| JDBC 노가다 탈출! Apache QueryRunner 완벽 가이드 (0) | 2026.03.14 |
| [Spring Boot] API 예외 처리의 정석: @RestControllerAdvice로 클린 코드 만들기 (0) | 2026.03.13 |
| [Java] "돈 계산할 때 double 쓰면 망합니다" — BigDecimal의 필요성과 사용법 (0) | 2026.03.11 |