새소식

반응형
Java/Spring

Spring Security 5에서 6로 변경된 필터 확인하기(SecurityContextPersistenceFilter, SecurityContextHolderFilter)

  • -
반응형

Spring Security 5 에서 6 버전으로 넘어가면서 변경된 SecurityContextPersistenceFilter 와 SecurityContextHolderFilter 를

알아보자

 

SecurityContextPersistenceFilter 인증 처리 과정

  1. 요청이 들어오면 SecurityContextRepository.loadContext() 를 호출해서 기존 인증 정보를 로딩한다.
    => 인증정보가 들어있는 SecurityContext를 가져옴
  2. 로딩된 SecurityContext를 SecurityContextHolderStrategy.setContext() 로 ThreadLocal 에 저장함
    => SecurityContextHolderStrategy 는 실제로 ThreadLocalSecurityContextHolderStrategy 클래스 사용
  3. 이후 필터 체인에서 인증 처리(UsernamePasswordAuthenticationFilter 등)가 해당 Context 사용
  4. 요청이 끝나면 finally 에서 SecurityContextHolderStrategy.getContext() 를 호출하여
    해당 SecurityContext 를 가져와 SecurityContextRepository 에 saveContext() 한다.
  5. SecurityContextHolderStrategy.clearContext() 를 통해 ThreadLocal 을 비운다.

 

@Deprecated
public class SecurityContextPersistenceFilter extends GenericFilterBean {
	private SecurityContextRepository repo;

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();
        
    public SecurityContextPersistenceFilter() {
		this(new HttpSessionSecurityContextRepository());
	}

	public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
		this.repo = repo;
	}
    
    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		// ensure that filter is only applied once per request
		if (request.getAttribute(FILTER_APPLIED) != null) {
			chain.doFilter(request, response);
			return;
		}
		request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
		if (this.forceEagerSessionCreation) {
			HttpSession session = request.getSession();
			if (this.logger.isDebugEnabled() && session.isNew()) {
				this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
			}
		}
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
		SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
		try {
			this.securityContextHolderStrategy.setContext(contextBeforeChainExecution);
			if (contextBeforeChainExecution.getAuthentication() == null) {
				logger.debug("Set SecurityContextHolder to empty SecurityContext");
			}
			else {
				if (this.logger.isDebugEnabled()) {
					this.logger
						.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
				}
			}
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
			SecurityContext contextAfterChainExecution = this.securityContextHolderStrategy.getContext();
			// Crucial removal of SecurityContextHolder contents before anything else.
			this.securityContextHolderStrategy.clearContext();
			this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);
			this.logger.debug("Cleared SecurityContextHolder to complete request");
		}
	}
}

 

 

위 내용을 보자면 SecurityContextHolderStrategy 기본 전략인 ThreadLocal 방식의

ThreadLocalSecurityContextHolderStrategy 클래스를 사용해 ThreadLocal 에 직접 저장한다.

즉, 직접적으로 ThreadLocal 을 사용하는 구조이다.

 

 

SecurityContextHolderFilter 인증 처리과정

SecurityContextPersistenceFilter 는 deprecated 되었고, 대신 SecurityContextHolderFilter 가 등장했다.

해당 필터는 SecurityContextHolder 가 직접 ThreadLocal 을 건드리지 않도록 설계되었다.

 

SecurityContextHolderFilter 는 SecurityContextRepository 를 통해 컨텍스트를 처리하면서도

SecurityContextHolder 와는 간접적으로만 상호작용한다.

 

주요 변경사항은 SecurityContextHolderStrategy 의 전략 패턴을 이용해서 SecurityContextHolder가

ThreadLocal을 직접 접근하지 않도록 분리한다.

 

  1. SecurityContextRepository.loadDeferredContext(request) 를 통해 SecurityContext를 호출한다.
    본인은 new HttpSessionSecurityContextRepository() 를 통해 설정했다.
  2. SecurityContextHolderStrategy.setDeferredContext(SecurityContext) 를 통해 
    SecurityContext 를 ThreadLocal 에 저장한다. (기본전략이 ThreadLocal 방식)
  3. 다음 인증필터내에서 SecurityContext 를 참조하여 인증 처리한다.
  4. SecurityContextHolderStrategy.clearContext() 를 호출하여 ThreadLocal 을 정리한다.

 

public class SecurityContextHolderFilter extends GenericFilterBean {
	private final SecurityContextRepository securityContextRepository;

	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
		.getContextHolderStrategy();
        
	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);
		}
	}
}

 

 

SecurityContextRepository 의 기본값은 HttpSessionSecurityContextRepository 이다.

.loadDeferredContext(request) 메서드를 통해 SecurtyContext 를 불러온다.

Session 내에 SpringSecurityContextKey 값으로 호출해  SecurityContext 를 불러온다.

SecurityContextHolderStrategy.setDeferredContext(seucirytContext) 를 통해

SecurityContext 를 저장하고 다음 인증필터로 간다.

인증필터가 종료된 이후 finally 를 통해 SecurityContextHolderStrategy의 clearContext() 호출하여

ThreadLocal 을 비운다.

 

반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.