[Spring Security] Persisting Authentication(영속성 인증)

이재성·2024년 4월 17일

이 내용은 [Spring Security Docs 6.2.4](docs.spring.io/spring-security/reference/servlet/authentication/persistence.html ) 및 소스 코드를 분석해 작성했습니다.

스프링 시큐리티를 사용하다 보면, 한 번 로그인 한 경우 로그인 정보 없이 HTTP API 를 요청해도 인증되는 경우가 있습니다. 이를 영속성 인증이라고 합니다. 여기에서는 어떻게 영속성 인증이 가능한지 알아보도록 하겠습니다.

SecurityContextHolderFilter

모든 API 요청은 SecurityContextHolderFilter 를 거치게 됩니다. 이 필터는 HTTP 요청 정보를 이용해 SecurityContext (인증 정보)를 가져옵니다.

image.png

SecurityContextRepository

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

image.png

image.png

SecurityContextRepository 구현체

SecurityContextRepositorySecurityContext를 저장하는 방식에 따라 여러 구현체가 존재합니다.

구현체저장 장소
HttpSessionSecurityContextRepositoryHttpSession
RequestAttributeSecurityContextRepositoryRequest Attribute

또한, 이런 구현체들을 복합적으로 사용할 수 있는 DelegatingSecurityContextRepository 구현제도 존재합니다. 이 구현체는 다수의 SecurityContextRepositorySecurityContext를 저장, 조회할 수 있습니다. 스프링 시큐리티는 기본적으로 DelegatingSecurityContextRepository 를 사용하고 있으며, 내부에는 두 개의 구현체를 저장하고 있습니다.

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 설정

필요에 따라서 SecurityContextRepository 인터페이스를 구현해 SecurityContext를 저장하는 위치를 정하고 설정할 수 있습니다. 예를 들어, Redis 에 인증 정보를 저장하고 조회하고 싶다면 다음과 같이 구현체를 만들어 설정하면됩니다.

public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new RedisSecurityContextRepository())
		);
	return http.build();
}

이상으로 스프링 시큐리티가 인증 정보 영속성을 구현하는 방법과 인증 정보 저장소를 커스텀 설정할 수 있는 방법을 알아보았습니다.

profile
벡엔드 개발을 하며 이유와 방법을 찾는 글 작성이 취미입니다.

0개의 댓글