Spring-Security

SecurityContextHolderFilter & SecurityContextRepository

neal89 2025. 5. 24. 12:35

1. SecurityContextHolderFilter

위치 및 역할

  • Spring Security 필터 체인의 가장 앞쪽(초기 단계)에 위치
  • 각 HTTP 요청 시작 전에 인증 정보를 담은 SecurityContext를 SecurityContextHolder(주로 ThreadLocal)에 로드
  • 요청 처리가 끝난 후 변경된 SecurityContext를 저장하거나, 필요 시 초기화(클리어)하는 역할 수행

동작 흐름

  • 요청 시작 전
    • 내부적으로 연결된 SecurityContextRepository의 loadContext()를 호출해 현재 요청에 맞는 SecurityContext를 가져옴
    • HTTP 세션 등에서 찾거나 없으면 빈 SecurityContext 생성 후 SecurityContextHolder에 저장
    • 이후 컨트롤러, 서비스, 다른 필터 등이 SecurityContextHolder.getContext()로 인증 정보 접근 가능
  • 요청 처리 중
    • 인증 필터나 권한 필터, 비즈니스 로직에서 인증 상태가 변경될 수 있음
    • 예: 인증 성공 시 SecurityContextHolder.getContext().setAuthentication() 으로 인증 정보 갱신
  • 요청 완료 후
    • doFilter() 종료 직전에 SecurityContextRepository.saveContext()를 호출해 현재 SecurityContext 저장
    • 세션 또는 다른 저장소에 저장하여 다음 요청에도 인증 상태 유지 가능
    • 마지막으로 SecurityContextHolder.clearContext() 호출해 ThreadLocal 초기화 (스레드 재사용 시 정보 유출 방지)

2. SecurityContextRepository

역할

  • 인증 정보를 저장하고 불러오는 전략(저장소 역할)을 담당하는 인터페이스
  • HTTP 요청 간 인증 상태 유지 방법 결정

주요 메소드

  • loadContext(HttpRequestResponseHolder) : 요청에서 SecurityContext 불러오기
  • saveContext(SecurityContext, HttpServletRequest, HttpServletResponse) : 인증 정보를 요청/응답에 저장
  • containsContext(HttpServletRequest) : 해당 요청에 인증 정보가 있는지 확인

주요 구현체 및 특징

  • HttpSessionSecurityContextRepository (기본 구현체)
    • 인증 정보를 HTTP 세션에 SPRING_SECURITY_CONTEXT 이름으로 저장
    • 세션 ID (보통 JSESSIONID 쿠키)를 통해 상태 유지
    • 인증 성공 시 세션에 저장, 익명 사용자나 빈 인증 정보는 저장하지 않을 수 있음
  • NullSecurityContextRepository
    • 인증 정보를 저장하지 않음
    • 무상태(Stateless) 인증 환경(예: JWT 토큰 기반 인증)에서 사용
    • 매 요청마다 토큰을 검증해 SecurityContext를 새로 만듦
  • DelegatingSecurityContextRepository
    • 여러 개의 SecurityContextRepository를 묶어서 사용하는 구현체
    • 내부적으로 여러 저장소에 동시에 저장하거나 순서대로 시도하는 복합 전략 지원
    • 예: 세션 저장소 + 캐시 저장소 조합

3. SecurityContextHolderFilter와 SecurityContextRepository 관계

  • SecurityContextHolderFilter가 인증 상태를 언제 로드하고 저장할지 관리 (요청의 시작과 끝)
  • SecurityContextRepository가 인증 상태를 어디에 저장/로드할지 관리 (세션, 데이터베이스, Redis 등 저장소)
  • 이 둘의 협력을 통해 세션 기반이든 토큰 기반이든 Spring Security의 인증 상태 관리가 원활히 이루어짐

4. SecurityContext 명시적 저장 설정 및 필터 작동 방식 예제

http
  .securityContext(securityContext -> securityContext
    .requireExplicitSave(true) // 기본값
  );
  • requireExplicitSave(true)
    • SecurityContextHolderFilter가 실행됨
    • 요청 완료 시 개발자가 명시적으로 SecurityContext 저장을 호출해야 함
  • requireExplicitSave(false)
    • SecurityContextHolderFilter 대신, SecurityContextPersistenceFilter와 유사한 방식으로 작동함
    • SecurityContextHolderFilter가 실행되고, 이 필터가 SecurityContextPersistenceFilter처럼 동작.
      --> 즉, 자동으로 SecurityContext를 로드하고 저장한다.
    • 요청 시작 시 SecurityContext 자동 로드, 요청 종료 시 자동 저장
    • 개발자가 명시적으로 저장할 필요 없이 자동으로 저장 처리됨

추가 설명

  • requireExplicitSave(false) 설정 시, 실제로 SecurityContextPersistenceFilter가 필터 체인에 들어가는 것이 아니라,
  • SecurityContextHolderFilter가 SecurityContextPersistenceFilter처럼 자동 저장 방식으로 동작하도록 설정되는 것임
  • 즉, 명시적 저장 호출 없이도 SecurityContext가 자동으로 관리됨

 

5. 명시적으로 SecurityContextRepository를 통해 SecurityContext를 저장

public class CustomAuthenticationFilter extends OncePerRequestFilter {

    private final SecurityContextRepository securityContextRepository;

    public CustomAuthenticationFilter(SecurityContextRepository securityContextRepository) {
        this.securityContextRepository = securityContextRepository;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {

        String token = request.getHeader("Authorization");

        if (token != null && token.equals("Bearer valid-token")) {
            // 인증 정보 생성
            Authentication authentication = new UsernamePasswordAuthenticationToken(
                "user", null,
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
            );

            // SecurityContext 생성 및 설정
            SecurityContext context = SecurityContextHolder.createEmptyContext();
            context.setAuthentication(authentication);
            SecurityContextHolder.setContext(context);

            // ✅ 명시적으로 저장
            securityContextRepository.saveContext(context, request, response);
        }

        filterChain.doFilter(request, response);
    }
}