Spring Security - 7. SecurityContextHolderFilter

하쮸·2025년 1월 29일
  • SecurityContextHolderFilter
    • SecurityContextRepository를 사용하여 SecurityContext를 가져오고 이를 SecurityContextHolder에 설정하는 필터.
      • 즉, SecurityContextHolderFilter의 목적은 SecurityContextRepository를 사용하여 현재 요청과 관련된 SecurityContext를 가져오고 이를 SecurityContextHolder에 설정하고 요청 종료 시 SecurityContext 초기화함.
    • SecurityContextPersistenceFilter와 유사하지만,
    SecurityContextRepository.saveContext(SecurityContext, HttpServletRequest, HttpServletResponse)
    위 코드를 명시적으로 호출해야만 SecurityContext가 저장됨.
    • 이 방식은 효율성을 향상시키고, 서로 다른 인증 메커니즘이 개별적으로 인증을 지속할지 여부를 선택할 수 있도록 함으로써 더 나은 유연성을 제공함.

↓ SecurityContextRepository

package org.springframework.security.web.context;

import java.util.function.Supplier;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.core.context.DeferredSecurityContext;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.function.SingletonSupplier;

/**
 * Strategy used for persisting a {@link SecurityContext} between requests.
 * <p>
 * Used by {@link SecurityContextPersistenceFilter} to obtain the context which should be
 * used for the current thread of execution and to store the context once it has been
 * removed from thread-local storage and the request has completed.
 * <p>
 * The persistence mechanism used will depend on the implementation, but most commonly the
 * <tt>HttpSession</tt> will be used to store the context.
 *
 * @author Luke Taylor
 * @since 3.0
 * @see SecurityContextPersistenceFilter
 * @see HttpSessionSecurityContextRepository
 * @see SaveContextOnUpdateOrErrorResponseWrapper
 */
public interface SecurityContextRepository {

	/**
	 * Obtains the security context for the supplied request. For an unauthenticated user,
	 * an empty context implementation should be returned. This method should not return
	 * null.
	 * <p>
	 * The use of the <tt>HttpRequestResponseHolder</tt> parameter allows implementations
	 * to return wrapped versions of the request or response (or both), allowing them to
	 * access implementation-specific state for the request. The values obtained from the
	 * holder will be passed on to the filter chain and also to the <tt>saveContext</tt>
	 * method when it is finally called to allow implicit saves of the
	 * <tt>SecurityContext</tt>. Implementations may wish to return a subclass of
	 * {@link SaveContextOnUpdateOrErrorResponseWrapper} as the response object, which
	 * guarantees that the context is persisted when an error or redirect occurs.
	 * Implementations may allow passing in the original request response to allow
	 * explicit saves.
	 * @param requestResponseHolder holder for the current request and response for which
	 * the context should be loaded.
	 * @return The security context which should be used for the current request, never
	 * null.
	 * @deprecated Use {@link #loadDeferredContext(HttpServletRequest)} instead.
	 */
	@Deprecated
	SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);

	/**
	 * Defers loading the {@link SecurityContext} using the {@link HttpServletRequest}
	 * until it is needed by the application.
	 * @param request the {@link HttpServletRequest} to load the {@link SecurityContext}
	 * from
	 * @return a {@link DeferredSecurityContext} that returns the {@link SecurityContext}
	 * which cannot be null
	 * @since 5.8
	 */
	default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {
		Supplier<SecurityContext> supplier = () -> loadContext(new HttpRequestResponseHolder(request, null));
		return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier),
				SecurityContextHolder.getContextHolderStrategy());
	}

	/**
	 * Stores the security context on completion of a request.
	 * @param context the non-null context which was obtained from the holder.
	 * @param request
	 * @param response
	 */
	void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);

	/**
	 * Allows the repository to be queried as to whether it contains a security context
	 * for the current request.
	 * @param request the current request
	 * @return true if a context is found for the request, false otherwise
	 */
	boolean containsContext(HttpServletRequest request);

}

↓ SecurityContextHolderFilter

package org.springframework.security.web.context;

import java.io.IOException;
import java.util.function.Supplier;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

/**
 * A {@link jakarta.servlet.Filter} that uses the {@link SecurityContextRepository} to
 * obtain the {@link SecurityContext} and set it on the {@link SecurityContextHolder}.
 * This is similar to {@link SecurityContextPersistenceFilter} except that the
 * {@link SecurityContextRepository#saveContext(SecurityContext, HttpServletRequest, HttpServletResponse)}
 * must be explicitly invoked to save the {@link SecurityContext}. This improves the
 * efficiency and provides better flexibility by allowing different authentication
 * mechanisms to choose individually if authentication should be persisted.
 *
 * @author Rob Winch
 * @author Marcus da Coregio
 * @since 5.7
 */
public class SecurityContextHolderFilter extends GenericFilterBean {

	private static final String FILTER_APPLIED = SecurityContextHolderFilter.class.getName() + ".APPLIED";

	private final SecurityContextRepository securityContextRepository;

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();

	/**
	 * Creates a new instance.
	 * @param securityContextRepository the repository to use. Cannot be null.
	 */
	public SecurityContextHolderFilter(SecurityContextRepository securityContextRepository) {
		Assert.notNull(securityContextRepository, "securityContextRepository cannot be null");
		this.securityContextRepository = securityContextRepository;
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
		try {
			this.securityContextHolderStrategy.setDeferredContext(deferredContext);
			chain.doFilter(request, response);
		}
		finally {
			this.securityContextHolderStrategy.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}

	/**
	 * Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
	 * the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
	 *
	 * @since 5.8
	 */
	public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

}
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
  • SecurityContextRepository인터페이스의 loadDeferredContext()메서드를 통해 현재 요청에 대한 유저의 정보를 불러옴.
		try {
			this.securityContextHolderStrategy.setDeferredContext(deferredContext);
			chain.doFilter(request, response);
		}
		finally {
			this.securityContextHolderStrategy.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
  • securityContextHolder에 setDeferredContext() 메서드를 통해 값을 저장시키고 chain.doFilter(request, response);을 통해 다음 필터로 전달함.
  • 그 이후 finally문에서 clearContext()을 사용해서 초기화시킴.

1-1. SecurityContextPersistenceFilter와 SecurityContextHolderFilter의 차이

Persisting Authentication

  • SecurityContextPersistenceFilter.

    • 이전 방식. (5.7 이후로 Deprecated)
      • 요청 처리 전에 SecurityContextRepository에서 SecurityContext를 불러와서 SecurityContextHolder에 설정.
      • 요청 처리 후 SecurityContext를 자동으로 저장
        • 즉, 인증 정보를 자동으로 저장하는 방식.
    • // SecurityContextPersistenceFilter
      SecurityContextHolder.setContext(securityContext);
  • SecurityContextHolderFilter.

    • 최신 방식.

      • SecurityContextRepository에서 SecurityContext를 불러와서 SecurityContextHolder에 설정하는 역할만 수행.
      • SecurityContext를 자동으로 저장하지 않고 사용자가 명시적으로 저장해야 함.
      SecurityContextRepository.saveContext()
      • 요청이 끝나면 securityContextHolderStrategy.clearContext()메서드를 호출하여 초기화시킴.
        • 즉, 더 유연한 인증 방식을 제공하며 사용자가 명시적으로 저장해야하는 방식.
    •   // SecurityContextHolderFilter
      SecurityContextHolder.setContext(securityContext);
      securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
profile
Every cloud has a silver lining.

0개의 댓글