728x90
반응형

- 보안 로직 추가 후 로그인이 즉시 풀리는 현상
최근 로그인 시 보안 강화를 위해 세션 고정(Session Fixation) 공격 방어 로직을 추가하던 중, 로그인이 성공하자마자 풀려버리는(인증이 유지되지 않는) 이슈를 겪었습니다.
겉보기엔 올바른 보안 로직 같아 보이지만, HttpSession의 생명주기와 인증 컨텍스트 처리 방식에 대한 오해에서 비롯된 흔한 실수였습니다. 해당 문제의 원인과 해결 방법을 정리하여 공유합니다.
1. 문제 상황
로그인 프로세스 도중, 세션 하이재킹을 방지하기 위해 기존 세션을 파기하고 새로운 세션을 발급하는 아래 코드를 추가했습니다. 그런데 이 코드가 실행된 직후 클라이언트가 비로그인 상태가 되는 현상이 발생했습니다.
// 문제의 코드: 세션 고정 공격 방어 로직
HttpSession currentSession = request.getSession(false);
if (currentSession != null) {
try {
// 기존 세션을 무효화 (여기서 문제 발생)
currentSession.invalidate();
} catch (IllegalStateException e) {
// ignore
}
}
// 새 세션 생성
currentSession = request.getSession(true);
2. 원인 분석
핵심 원인은 "세션 객체의 생명주기가 종료되면서, 그 안에 저장된 인증 정보도 함께 소멸했기 때문"입니다.
웹 애플리케이션의 일반적인 로그인 흐름은 다음과 같습니다.
- 인증 성공: ID/PW 검증 후, 서버는 사용자 정보가 담긴 인증 객체를 HttpSession에 저장합니다.
- 세션 무효화 (invalidate()): 위 코드에서 invalidate()를 호출하는 순간, 1번에서 저장한 인증 객체를 포함한 모든 세션 속성(Attribute)이 메모리에서 삭제됩니다.
- 새 세션 생성: request.getSession(true)로 얻은 세션은 완전히 비어있는 깡통 세션입니다.
- 결과: 서버 입장에서 이 사용자는 인증 정보를 잃어버린 '익명 사용자'일 뿐입니다.
즉, "방어벽을 높이려다 집 열쇠까지 버리고 새 집으로 이사 간 꼴"이 된 것입니다.
3. 해결 방법
방법 A. Servlet 3.1+ changeSessionId() 사용
가장 깔끔하고 현대적인 방법입니다. Servlet 3.1(Tomcat 8 이상)부터는 세션 내부의 데이터(Attribute)는 그대로 유지하면서, Session ID만 교체해주는 메서드를 제공합니다.
HttpServletRequest request = ...;
if (request.getSession(false) != null) {
// 기존 데이터(인증 정보 등)는 유지하고 ID만 변경하여 세션 고정 공격 방어
request.changeSessionId();
}
방법 B. Spring Security 설정 활용
Spring Security를 사용 중이라면 비즈니스 로직에 굳이 세션 처리 코드를 넣을 필요가 없습니다. 프레임워크 레벨에서 이를 지원합니다.
// SecurityConfig 설정
http.sessionManagement()
.sessionFixation().changeSessionId(); // 기본값은 migrateSession()
방법 C. 수동 마이그레이션 (Legacy 환경)
changeSessionId()를 사용할 수 없는 구버전 환경이라면, invalidate() 호출 전에 속성들을 백업하고 복원하는 절차가 필요합니다.
// 1. 기존 속성 백업
Map<String, Object> attributes = new HashMap<>();
Enumeration<String> names = oldSession.getAttributeNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
attributes.put(name, oldSession.getAttribute(name));
}
// 2. 세션 재생성
oldSession.invalidate();
HttpSession newSession = request.getSession(true);
// 3. 속성 복구 (인증 정보 포함)
attributes.forEach(newSession::setAttribute);728x90
반응형