이 내용은 [Spring Security Docs 6.2.4](docs.spring.io/spring-security/reference/servlet/authentication/persistence.html ) 및 소스 코드를 분석해 작성했습니다.
스프링 시큐리티를 사용하다 보면, 한 번 로그인 한 경우 로그인 정보 없이 HTTP API 를 요청해도 인증되는 경우가 있습니다. 이를 영속성 인증이라고 합니다. 여기에서는 어떻게 영속성 인증이 가능한지 알아보도록 하겠습니다.
모든 API 요청은 SecurityContextHolderFilter 를 거치게 됩니다. 이 필터는 HTTP 요청 정보를 이용해 SecurityContext (인증 정보)를 가져옵니다.

SecurityContextHolderFilter는 인증 정보를 가져올때는 직접 가져오는게 아니라 인증 정보를 조회하는 SecurityContextRepository 인터페이스 구현체를 사용합니다. 아래 코드에서 SecurityContextHolderFilter 를 생성할 때 인터페이스를 주입받는 것을 확인할 수 있습니다.


SecurityContextRepository 는 SecurityContext를 저장하는 방식에 따라 여러 구현체가 존재합니다.
| 구현체 | 저장 장소 |
|---|---|
| HttpSessionSecurityContextRepository | HttpSession |
| RequestAttributeSecurityContextRepository | Request Attribute |
또한, 이런 구현체들을 복합적으로 사용할 수 있는 DelegatingSecurityContextRepository 구현제도 존재합니다. 이 구현체는 다수의 SecurityContextRepository에 SecurityContext를 저장, 조회할 수 있습니다. 스프링 시큐리티는 기본적으로 DelegatingSecurityContextRepository 를 사용하고 있으며, 내부에는 두 개의 구현체를 저장하고 있습니다.

아래 코드는 DelegatingSecurityContextRepository의 SecurityContext 로드 코드입니다. 내부에 저장된 SecurityContextRepository 구현체들을 순회하면서 SecurityContext 를 조회하고 하나라도 조회되면 반환하는 모습을 볼 수 있습니다.
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
SecurityContext result = null;
Iterator var3 = this.delegates.iterator();
while(true) {
SecurityContextRepository delegate;
SecurityContext delegateResult;
do {
if (!var3.hasNext()) {
return result;
}
delegate = (SecurityContextRepository)var3.next();
delegateResult = delegate.loadContext(requestResponseHolder);
} while(result != null && !delegate.containsContext(requestResponseHolder.getRequest()));
result = delegateResult;
}
}
아래 코드는 SecurityContext 저장 코드입니다. 모든 구현체에 인증 정보를 저장하고 있는 모습입니다.
⚠️ 주의 : 이런 로직 때문에, 만약 인증 정보를 해지하기 위해 하나의 저장 장소에서만 인증 정보를 삭제해도 다른 저장소에 인증 정보가 남아있게 된다면 인증이 그대로 수행될 수 있습니다.
public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
Iterator var4 = this.delegates.iterator();
while(var4.hasNext()) {
SecurityContextRepository delegate = (SecurityContextRepository)var4.next();
delegate.saveContext(context, request, response);
}
}
필요에 따라서 SecurityContextRepository 인터페이스를 구현해 SecurityContext를 저장하는 위치를 정하고 설정할 수 있습니다. 예를 들어, Redis 에 인증 정보를 저장하고 조회하고 싶다면 다음과 같이 구현체를 만들어 설정하면됩니다.
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RedisSecurityContextRepository())
);
return http.build();
}
이상으로 스프링 시큐리티가 인증 정보 영속성을 구현하는 방법과 인증 정보 저장소를 커스텀 설정할 수 있는 방법을 알아보았습니다.