SecurityContextHolder 는 Request 요청이 종료된후 제거가 되는가 ?

XingXi·2024년 6월 27일
0

기록

목록 보기
29/33

SPRING SECURITY

알아보게 된 계기

@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter {

    private final JwtHandler handler;

    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String jwt = resolveTkn(request);
        String uri = request.getRequestURI();

        if(hasText(jwt))
        {
            Authentication authentication = handler.getAuthentication(jwt);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request,response);
    }

    private String resolveTkn(HttpServletRequest request){
        String bearerTkn = request.getHeader("Authorization");

        if(StringUtils.hasText(bearerTkn) && bearerTkn.startsWith("Bearer ")) return bearerTkn.substring(7);
        return null;
    }

현재 혼자서 진행하는 프로젝트의 코드 중 일부이다.

JWT 를 사용할 때 버릇처럼 적던 코드이다.
→ 요청이 들어오고 Header 에 JWT 토큰이 포함이 되어 있고, 해당 토큰이 이상이 없으면 SecurityContextHolder 에 Authentication 객체를 할당하고, 다음 필터로 넘어가는 코드이다.

여기서 새로운 요청이 들어오면 이전에 요청했던 Authentication 객체는 어떻게 되는지 확인한 적이 없던것 같아 이번 기회에 알아보기로 했다.

우선 Spring Security 에서 저장하려는 값에 대한 설명이다.


Authentication

→ Spring Security 에서 인증 및 인가를 위해 사용되는 객체로

  • principal : 정보
  • credentials : 암호
  • authorities : 권한
    위 3가지 속성을 저장한다.

SecurityContextHolder

  • SecurityContext 를 저장하고 strategy 에 따라 SecurityContext 접근 방법을 지정하는 객체이다.

SecurityContext

  • 현재 인증된 사용자의 Authentication 객체를 저장하며 SecurityContextHolder 에 속해 있는 영역이다.
    SecurityContextHolder 는 스프링 시큐리티에서 인증된 사용자의 정보를 저장한다.
    → SecurityContextHolder 의 전략 에 따라 SecurityContext 저장 방식이 다르긴 하지만
    보통 ThreaLocal 에 저장이 된다.

SecurityContextHolder.class

public class SecurityContextHolder {
...

    private static void initializeStrategy() {
        if ("MODE_PRE_INITIALIZED".equals(strategyName)) {
            Assert.state(strategy != null, "When using MODE_PRE_INITIALIZED, setContextHolderStrategy must be called with the fully constructed strategy");
        } else {
            if (!StringUtils.hasText(strategyName)) {
                strategyName = "MODE_THREADLOCAL";
            }

            if (strategyName.equals("MODE_THREADLOCAL")) {
                strategy = new ThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_INHERITABLETHREADLOCAL")) {
                strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
            } else if (strategyName.equals("MODE_GLOBAL")) {
                strategy = new GlobalSecurityContextHolderStrategy();
            } else {
                try {
                    Class<?> clazz = Class.forName(strategyName);
                    Constructor<?> customStrategy = clazz.getConstructor();
                    strategy = (SecurityContextHolderStrategy)customStrategy.newInstance();
                } catch (Exception var2) {
                    ReflectionUtils.handleReflectionException(var2);
                }

            }
        }
    }

여기서 일반적으로 ThreadLocalSecurityContextHolderStrategy 전략을 선택하는 것을 확인할 수 있으며,
ThreadLocalSecurityContextHolderStrategy.class 를 자세히 들여다 보면

ThreadLocalSecurityContextHolderStrategy.class

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

	private static final ThreadLocal<Supplier<SecurityContext>> contextHolder = new ThreadLocal<>();

	@Override
	public void clearContext() {
		contextHolder.remove();
	}

ThreadLocal 에 ContextHolder 를 저장하는 것을 확인할 수 있다.


요약해보면

Spring Security 에서 인증/인가를 구현하기 위한 요소들을 저장하는 Authentication 객체를 저장하는 영역이 SecurityContext 이고
SecurityContext 를 포함하고 있는 SecurityContextHolder 의 startegy 에 따라
저장되는 방식 및 접근 하는 메커니즘이 달라진다.

여기서 저장되는 방식이란 한번에 요청에 인증된 사용자의 정보 저장 방식을 말한다.

JWT 와 함께 SpringSecurity 를 사용할 때는 Stateless 방식, 즉 요청 한번 이후에 사용자의 정보를 저장하지 않기 때문에
기본적으로 ThreadLocal 에 SecurityContext 를 저장하여 요청이 종료 되면
ThreadLocal 에 저장된 SecurityContext 정보를 초기화 하는 구조로 구성 되어야 한다.

ThreadLocal 은 clear 해주어야한다.

→ ThreadPool 을 사용하고 있는 환경이면 특히 ThreadLocal 에 저장된 정보는 제거해주어야한다.
다음 사용자가 사용할 때, 이전 사용자의 정보가 노출 될 수 있기 때문이다.
SpringBoot 에서 동작하는 Spring Security 도 요청이 종료된 후 Thread Local 에 저장된 정보를 제거해주어야한다.

SecurityContextHolderFilter 에서 clear

SecurityContextPersistenceFilter가 Spring 5.7.2부터 
@Deprecated 되면서 SecurityContextHolderFilter 를 사용하게 되었다.

 *
 * @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;
	}
}

여기 코드를 보면

			this.securityContextHolderStrategy.clearContext();
			request.removeAttribute(FILTER_APPLIED);

이 부분이 있는데 SecurityContext ThreadLocal 에서 지워주는 부분이다.
모든 요청이 종료 되고 나서 ThreadLocal 에 저장되어 진 SecurityContext 를 지워주는 것을 확인할 수 있다.

결론적으로

SecurityContextHolderFilter 에서 ThreadLocal 에 저장된 SecurityContext 를 제거해서 다른 사용자에게 정보가 노출되는 것을 방지하여 사용할 수 있다.

0개의 댓글