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);
}
}