1. Session Management란?

Spring Security의 세션 관리는 사용자의 세션을 생성, 유지, 파괴하는 전반적인 프로세스를 관리합니다. 이는 보안과 사용자 경험 모두에 중요한 영향을 미칩니다. 또한 JWT이나 Session 관리를 하지않는 API를 사용하기전에 알아두어야 하기때문에 공부해보도록 하겠습니다.

 

 

 

2. Session Creation Policy (세션 생성 정책)

        http.sessionManagement(sessionManagement -> sessionManagement
            .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)     // 필요한 경우에만 생성(Default)
        );

 

정책 설명
SessionCreationPolicy.ALWAYS 항상 세션 생성
SessionCreationPolicy.IF_REQUIRED 필요한 경우에만 생성 (Default)
SessionCreationPolicy.NEVER 생성하지 않지만 존재하면 사용
SessionCreationPolicy.STATELESS 세션을 완전히 사용하지 않음 (JWT 등의 토큰 기반 인증에 적합)

 

 

3. Concurrent Session Control (동시 세션 제어)

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}
        http.sessionManagement(sessionManagement -> sessionManagement
            .maximumSessions(1)                     // 사용자당 최대 세션 수
            .maxSessionsPreventsLogin(true)         // true: 새로운 로그인 차단, false: 기존 세션 만료(기본값)
            .expiredUrl("/session-expired")         // 세션 만료시 이동할 URL
        );

 

3-1. HttpSessionEventPublisher

HttpSessionEventPublisher는 세션 생명주기 이벤트를 Spring의 ApplicationContext에 발행하는 역할을 합니다.

@Component
public class SessionEventListener {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @EventListener
    public void handleSessionCreated(HttpSessionCreatedEvent event) {
        HttpSession session = event.getSession();
        logger.info("새 세션 생성: {}", session.getId());
    }

    @EventListener
    public void handleSessionDestroyed(HttpSessionDestroyedEvent event) {
        HttpSession session = event.getSession();
        logger.info("세션 파괴됨: {}", session.getId());
    }

}

 

세션 모니터링

@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}
@Bean
public SessionRegistry sessionRegistry() {
    return new SessionRegistryImpl();
}

        http.sessionManagement(session -> session
            .sessionRegistry(sessionRegistry())
        );
@Component
@Slf4j
@RequiredArgsConstructor
public class SessionEventListener {

    private final SessionRegistry sessionRegistry;

    @EventListener
    public void handleSessionDestroyed(HttpSessionDestroyedEvent event) {
        log.info("세션만료 : {}", event.getSession().getId());
        // 세션이 만료되면 해당 사용자의 세션 정보 정리
        SecurityContext securityContext = (SecurityContext) event.getSession()
            .getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        if (securityContext != null) {
            sessionRegistry.removeSessionInformation(event.getSession().getId());
        }
    }

    @EventListener
    public void handleSessionCreated(HttpSessionCreatedEvent event) {
        log.info("세션생성 : {}", event.getSession().getId());
        HttpSession session = event.getSession();
        // 세션 생성 시 기본 타임아웃 설정
        session.setMaxInactiveInterval(60 * 30); // 30분
    }

}

 

3-2. 이전 세션 만료 전략

.maximumSessions(1)
.maxSessionsPreventsLogin(false)  // 기본값

 

3-3. 새 로그인 차단 전략

.maximumSessions(1)
.maxSessionsPreventsLogin(true)

 

 

4. Session Fixation Protection (세션 고정 보호)

 

4-1. 세션 고정 보호가 뭘까?

정확히는 세션 고정 공격 보호 (Session Fixation Attack Protection) 입니다. 세션 고정 공격은 악의적인 공격자가 사이트에 접속해서 세션을 생성한 다음 동일한 세션으로 다른 사용자가 접속하도록 유도할 수 있는 잠재적인 공격입니다. (예, 세션 식별자를 매개변수로 포함하는 링크를 전송) Spring Security는 사용자가 로그인할 때 새 세션을 생성하거나 세션 ID를 변경하여 이를 자동으로 보호하는데 이를 설정하는 것이 sessionFixation 설정입니다.

출처 : https://blog.pollra.com/odop-day-76

 

4-2. 세션 고정 보호 설정

        http.sessionManagement(sessionManagement -> sessionManagement
            .sessionFixation(fixation -> fixation
                .changeSessionId() // 세션 ID만 변경 (Servlet 3.1+ 컨테이너 기본값)
            )
        );

 

 

메소드 설명
newSession() 완전히 새로운 세션 생성
migrateSession() 이전 세션 속성을 새 세션으로 복사 (Servlet 3.0이하 Default)
changeSessionId() 세션 ID만 변경 (Servlet 3.1 이상 Default)
none() 보호 비활성화

 

 

 

5. Session Timeout (세션 타임아웃)

세션은 자체적으로 만료되며 Security Context가 제거되도록 해야할 것은 없습니다. 그러므로 세션이 만료된 시점을 감지하고 사용자가 특정 작업을 수행할 수 있도록 엔드포인트를 리디렉션할 수 있게만 해주면됩니다. 이는 invalidSessionUrl 에서 이루어집니다.

 

        http.sessionManagement(session -> session
            .invalidSessionUrl("/invalidSession") // 만료된 세선이 접근할 경우 "/invalidSession" 으로 이동
        );

 

 

        http.sessionManagement(session -> session
            .invalidSessionStrategy(new CustomInvalidSessionStrategy())
        );
        
public class CustomInvalidSessionStrategy implements InvalidSessionStrategy {

    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException {
        // Session 무효화
        HttpSession session = request.getSession(false);
        if (session != null) {
            session.invalidate();
        }

        // JSESSIONID 쿠키 삭제
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if ("JSESSIONID".equals(cookie.getName())) {
                    cookie.setValue("");
                    cookie.setPath("/");
                    cookie.setMaxAge(0);
                    response.addCookie(cookie);
                    break;
                }
            }
        }
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("<script>alert('세션이 만료되었습니다.');</script>");
        out.flush();
    }

}

 

invalidSessionUrl과 invalidSessionStrategy는 동시에 사용할 수 없습니다. 둘다 정의 되어있다면 invalidSessionStrategy만 적용됩니다. 그냥 세션 만료시 이동을 원할경우 invalidSessionUrl을 사용하고, 복잡한 로직이 추가되어야 한다면 invalidSessionStrategy를 사용하시면 됩니다.

 

 

 

주의사항

로그아웃 시에 쿠키가 제대로 삭제되지 않아 문제가 발생할 수 있습니다.

        http.logout(logout -> logout
            // 모든 쿠키 제거
            .addLogoutHandler(new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.COOKIES)))
            // 특정 쿠키 제거
            .deleteCookies("JSESSIONID", "remember-me")
        );

이렇게 명시적으로 쿠키를 제거해줍시다.

ClearSiteData를 지원하지 않는 브라우저에서는 정상적으로 동작하지 않을 수 있습니다.

 

 

 

 

6. SessionManagementFilter

sessionManagement는 SessionManagementFilter에서 사용됩니다. 그 역할로는 

 

  • 세션 생성 전략 관리
  • 동시 세션 제어
  • 세션 고정 보호
  • 유효하지 않은 세션 처리

우리가 위에서 알아본 것과 같습니다.

 

6-1. SessionManagementFilter 구조

  • SecurityContextRepository의 내용을 현재 SecurityContextHolder의 내용과 비교 검사
  • 이를 통해 현재 요청 중에 사용자가 인증되었는지 확인
  • 주로 pre-authentication이나 remember-me와 같은 비대화형 인증 메커니즘에서 사용됨

처리 흐름

  • SecurityContextRepository에 Security Context가 있다면 아무 작업도 하지 않음
  • SecurityContextRepository에 context가 없고, SecurityContext가 Authentication 객체를 포함하고 있다면 (익명객체가 아닌)
    • 이전 필터에서 이미 인증되었다고 가정
    • 설정된 SessionAuthenticationStrategy를 실행

미인증 사용자 처리

  • 유효하지 않은 세션 ID가 요청되었는지 확인 (예: 타임아웃으로 인한 유효하지 않은 세션)
  • 설정된 InvalidSessionStrategy가 있다면 이를 실행

 

6-2. SecurityContextRepository

SecurityContextRepository는 SecurityContext를 저장하고 불러오는 역할을 하는 인터페이스입니다. 주로 HTTP 세션에 SecurityContext를 저장하고 검색하는 작업을 담당합니다. 기본객체는 HttpSessionSecurityContextRepository 입니다.

기본 구현

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.securityContext((securityContext) -> securityContext
            .securityContextRepository(new HttpSessionSecurityContextRepository())
        );
        return http.build();
    }
}​


DelegatingSecurityContextRepository 사용 시

@Bean
public SecurityContextRepository securityContextRepository() {
    return new DelegatingSecurityContextRepository(
        new HttpSessionSecurityContextRepository(),
        new RequestAttributeSecurityContextRepository()
    );
}​

 

 

 

7. 보안설정

server.servlet.session.cookie.secure=true
server.servlet.session.cookie.http-only=true

 

server.servlet.session.cookie.secure=true

  • HTTPS를 통해서만 쿠키가 전송되도록 설정
  • HTTP로 요청시 쿠키가 전송되지 않음
  • 중간자 공격(Man-in-the-Middle Attack) 방지
  • 쿠키 탈취 위험 감소

server.servlet.session.cookie.http-only=true

  • JavaScript를 통한 쿠키 접근을 차단
  • XSS(Cross-Site Scripting) 공격 방지
  • 클라이언트 측 스크립트에서 document.cookie로 접근 불가

 

7-1. 추가 설정

# 쿠키 만료 시간 설정
server.servlet.session.timeout=30m

# 쿠키 경로 설정
server.servlet.session.cookie.path=/

# 쿠키 도메인 설정
server.servlet.session.cookie.domain=example.com

# 쿠키 이름 변경
server.servlet.session.cookie.name=MYSESSIONID

# SameSite 설정
server.servlet.session.cookie.same-site=strict

 

 

 

글이 너무 길어져서 여기까지 작성하겠습니다.

SessionManagement 관련된 내용은 어마어마하게 많습니다. 다음글에서는 이 글을 토대로 어떻게 적용하는지에 대해서 알아보도록하겠습니다.

+ Recent posts