Java & Spring/Spring Security

04. 인증 상태 영속성

승주우에요 2026. 8. 4. 13:46
인증 상태 영속성 - Spring Security

인증 상태 영속성

SecurityContext의 저장, 로드, 삭제 메커니즘과 요청 간 인증 유지 전략

인증 상태 영속성은 사용자가 한번 인증을 하면 이후 요청에서도 인증이 유지되는 메커니즘이다. 사용자의 인증 정보와 권한이 SecurityContext에 저장되고, HttpSession을 통해 요청 간 영속이 이루어진다. 이 챕터에서는 SecurityContextRepository의 구현체, SecurityContextHolderFilter의 동작 방식, 그리고 커스텀 인증 필터에서의 영속성 처리를 다룬다.

1. SecurityContextRepository

개념

SecurityContextRepository는 사용자가 인증을 한 이후 요청에 대해 계속 사용자의 인증을 유지하기 위해 사용되는 인터페이스다.

인증 요청과 인증 후 요청의 차이

flowchart TD subgraph AUTH_REQ["인증 요청"] direction TB C1["Client
GET /login"]:::client AF["AuthenticationFilter"]:::filter SC1["SecurityContext
+ Authentication"]:::sc SCR1["SecurityContextRepository"]:::repo HS1["HttpSession
SecurityContext 저장"]:::session C1 --> AF AF -->|"인증 완료"| SC1 SC1 -->|"명시적으로 저장"| SCR1 SCR1 --> HS1 end subgraph AFTER_REQ["인증 후 요청"] direction TB C2["Client
GET /user"]:::client SCHF["SecurityContextHolderFilter"]:::filter SCR2["SecurityContextRepository"]:::repo HS2["HttpSession"]:::session SC2["SecurityContext
+ Authentication"]:::sc C2 --> SCHF SCHF -->|"컨텍스트 로드"| SCR2 SCR2 --> HS2 HS2 -->|"세션에서 가져옴"| SC2 end classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef sc fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef repo fill:#5c3d00,stroke:#d29922,color:#ffffff classDef session fill:#2d333b,stroke:#768390,color:#e6edf3

SecurityContextRepository 인터페이스

메서드설명
containsContext(HttpServletRequest)현재 사용자를 위한 보안 컨텍스트가 저장소에 있는지 여부를 조회
saveContext(SecurityContext, HttpServletRequest, HttpServletResponse)인증 요청 완료 시 보안 컨텍스트를 저장한다
loadDeferredContext(HttpServletRequest)로딩을 지연시켜 필요 시점에 SecurityContext를 가져온다

구현체

구현체설명
HttpSessionSecurityContextRepository요청 간에 HttpSession에 보안 컨텍스트를 저장한다. 후속 요청 시 컨텍스트 영속성을 유지한다
RequestAttributeSecurityContextRepositoryServletRequest에 보안 컨텍스트를 저장한다. 후속 요청 시 컨텍스트 영속성을 유지할 수 없다
NullSecurityContextRepository세션을 사용하지 않는 인증(JWT, OAuth2)일 경우 사용하며 컨텍스트 관련 아무런 처리를 하지 않는다
DelegatingSecurityContextRepositoryRequestAttribute와 HttpSession 두 Repository를 동시에 사용할 수 있도록 위임하는 클래스. 초기화 시 기본으로 설정된다

2. SecurityContextHolderFilter

개념

SecurityContextHolderFilterSecurityContextRepository를 사용하여 SecurityContext를 얻고 이를 SecurityContextHolder에 설정하는 필터 클래스다.

이 필터는 SecurityContextRepository.saveContext()를 강제로 실행시키지 않고, 사용자가 명시적으로 호출해야 SecurityContext를 저장할 수 있다. 이 점이 SecurityContextPersistenceFilter와 다르다.

인증이 지속되어야 하는지를 각 인증 메커니즘이 독립적으로 선택할 수 있게 하여 더 나은 유연성을 제공하고, HttpSession에 필요할 때만 저장함으로써 성능을 향상시킨다.

SecurityContext 생성, 저장, 삭제 시나리오

시나리오동작
익명 사용자SecurityContextRepository로 새로운 SecurityContext를 생성하여 SecurityContextHolder에 저장 후 다음 필터로 전달. AnonymousAuthenticationFilter에서 AnonymousAuthenticationToken을 SecurityContext에 저장
인증 요청SecurityContextRepository로 새로운 SecurityContext를 생성하여 SecurityContextHolder에 저장 후 다음 필터로 전달. UsernamePasswordAuthenticationFilter에서 인증 성공 후 SecurityContext에 Authentication 저장. SecurityContextRepository를 사용하여 HttpSession에 SecurityContext 저장
인증 후 요청SecurityContextRepository를 사용하여 HttpSession에서 SecurityContext를 꺼내어 SecurityContextHolder에 저장 후 다음 필터로 전달. SecurityContext 안에 Authentication 객체가 존재하면 인증 유지
클라이언트 응답 시SecurityContextHolder.clearContext()로 컨텍스트를 삭제한다. 스레드 풀 환경에서는 반드시 필요

SecurityContextHolderFilter 흐름

flowchart TD C["Client
request"]:::client --> SCHF["SecurityContextHolderFilter"]:::filter SCHF --> SCR["SecurityContextRepository"]:::repo SCR --> HS["HttpSession"]:::session HS --> CHK{"SecurityContext
존재하는가?"} CHK -->|"Y"| EXIST["SecurityContextHolder
SecurityContext + Authentication"]:::success CHK -->|"N"| NEW["SecurityContextHolder
SecurityContext (null)"]:::empty EXIST --> CHAIN["chain.doFilter()"]:::chain NEW --> CHAIN CHAIN --> AF["AuthenticationFilter"]:::filter AF -->|"인증 성공"| SC_AUTH["SecurityContext
+ Authentication"]:::success SC_AUTH --> SCR2["SecurityContextRepository"]:::repo SCR2 -->|"세션에 저장"| HS2["HttpSession
SecurityContext"]:::session CHAIN --> CLEAR["SecurityContextHolder
clearContext()"]:::fail classDef client fill:#1c3a5f,stroke:#58a6ff,color:#ffffff classDef filter fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef repo fill:#5c3d00,stroke:#d29922,color:#ffffff classDef session fill:#2d333b,stroke:#768390,color:#e6edf3 classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff classDef empty fill:#5c1a1a,stroke:#f85149,color:#ffffff classDef chain fill:#2d333b,stroke:#768390,color:#e6edf3 classDef fail fill:#5c1a1a,stroke:#f85149,color:#ffffff

3. SecurityContextHolderFilter vs SecurityContextPersistenceFilter

항목SecurityContextHolderFilterSecurityContextPersistenceFilter
SecurityContext 로드SecurityContextRepository에서 로드SecurityContextRepository에서 로드
SecurityContext 저장저장하지 않음 (사용자가 명시적으로 호출)응답 시점에 Session에 자동 저장
상태현재 사용 (권장)Deprecated
현재 SecurityContextPersistenceFilter는 Deprecated 되었기 때문에 레거시 시스템 외에는 SecurityContextHolderFilter를 사용한다.

4. securityContext() API

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

    http.securityContext(securityContext -> securityContext
        .requireExplicitSave(true)  // SecurityContext를 명시적으로 저장할 것인지 여부 설정
                                    // 기본값은 true
                                    // true이면 SecurityContextHolderFilter 사용
                                    // false이면 SecurityContextPersistenceFilter 사용
    );

    return http.build();
}
requireExplicitSave(true)가 기본값이다. 이 설정에서는 인증 필터가 인증 성공 후 직접 SecurityContextRepository.saveContext()를 호출해야 SecurityContext가 세션에 저장된다.

5. 커스텀 인증 필터에서의 SecurityContextRepository

개념

커스텀 인증 필터를 구현할 경우 인증이 완료된 후 SecurityContext를 SecurityContextHolder에 설정한 후 SecurityContextRepository에 저장하기 위한 코드를 명시적으로 작성해야 한다.

// 커스텀 인증 필터에서 SecurityContext 저장 (필수)
securityContextHolderStrategy.setContext(context);
securityContextRepository.saveContext(context, request, response);

securityContextRepositoryHttpSessionSecurityContextRepository 또는 DelegatingSecurityContextRepository를 사용하면 된다.

6. 스프링 MVC 로그인 구현

개념

스프링 시큐리티 필터에 의존하는 대신 수동으로 사용자를 인증하는 경우 스프링 MVC 컨트롤러 엔드포인트를 사용할 수 있다. 요청 간에 인증을 저장하고 싶다면 HttpSessionSecurityContextRepository를 사용하여 인증 상태를 저장할 수 있다.

SecurityContextRepository securityContextRepository =
    new HttpSessionSecurityContextRepository();

@PostMapping("/login")
public void login(@RequestBody LoginRequest loginRequest,
                  HttpServletRequest request,
                  HttpServletResponse response) {

    // 1. 사용자 이름과 비밀번호를 담은 인증 객체를 생성한다
    UsernamePasswordAuthenticationToken token =
        UsernamePasswordAuthenticationToken.unauthenticated(
            loginRequest.getUsername(), loginRequest.getPassword());

    // 2. 인증을 시도하고 최종 인증 결과를 반환한다
    Authentication authentication =
        authenticationManager.authenticate(token);

    // 3. 인증 결과를 컨텍스트에 저장한다
    SecurityContext securityContext =
        SecurityContextHolder.getContextHolderStrategy().createEmptyContext();
    securityContext.setAuthentication(authentication);

    // 4. 컨텍스트를 ThreadLocal에 저장한다
    SecurityContextHolder.getContextHolderStrategy()
        .setContext(securityContext);

    // 5. 컨텍스트를 세션에 저장해서 인증 상태를 영속한다
    securityContextRepository.saveContext(
        securityContext, request, response);
}

각 단계별 역할

flowchart TD A["① UsernamePasswordAuthenticationToken 생성
(username + password)"]:::token_before A --> B["② AuthenticationManager.authenticate()
인증 시도 및 결과 반환"]:::manager B --> C["③ SecurityContext.setAuthentication()
인증 결과를 컨텍스트에 저장"]:::process C --> D["④ SecurityContextHolder.setContext()
컨텍스트를 ThreadLocal에 저장"]:::process D --> E["⑤ SecurityContextRepository.saveContext()
컨텍스트를 세션에 저장하여 영속"]:::success classDef token_before fill:#5c3d00,stroke:#d29922,color:#ffffff classDef manager fill:#3d1f5c,stroke:#bc8cff,color:#ffffff classDef process fill:#2d333b,stroke:#768390,color:#e6edf3 classDef success fill:#1a3a2a,stroke:#3fb950,color:#ffffff
5단계(saveContext)를 빠뜨리면 인증은 성공하지만 세션에 저장되지 않아 다음 요청에서 인증이 유지되지 않는다. SecurityContextHolderFilter는 자동 저장을 하지 않으므로 반드시 명시적으로 호출해야 한다.

정리

  • SecurityContextRepository — SecurityContext를 저장하고 로드하는 인터페이스. 기본 구현체로 HttpSessionSecurityContextRepository(세션 저장)와 DelegatingSecurityContextRepository(기본 설정)가 있다
  • SecurityContextHolderFilter — SecurityContextRepository에서 SecurityContext를 로드하여 SecurityContextHolder에 설정하는 필터. saveContext()를 자동으로 호출하지 않으므로 명시적 저장이 필요하다
  • SecurityContextPersistenceFilter — 응답 시점에 자동으로 세션에 저장하던 레거시 필터. 현재 Deprecated
  • requireExplicitSave(true) — 기본값. SecurityContextHolderFilter를 사용하며, 인증 필터가 직접 saveContext()를 호출해야 한다
  • 커스텀 인증 필터 — 인증 완료 후 반드시 SecurityContextHolder에 설정하고 SecurityContextRepository에 명시적으로 저장해야 한다
  • 스프링 MVC 로그인 — 필터 대신 컨트롤러에서 직접 인증할 때도 SecurityContextRepository.saveContext()를 호출해야 요청 간 인증이 유지된다

'Java & Spring > Spring Security' 카테고리의 다른 글

06. 예외 처리  (0) 2026.08.05
05. 세션 관리  (0) 2026.08.05
03. 인증 아키텍처  (0) 2026.08.04
02. 인증 프로세스  (0) 2026.08.01
01. 초기화 과정 이해  (0) 2026.08.01