SecurityContext를 새로 만들어야 할까?
궁금했던 점
JWT 검증 필터 부분 코드를 리팩토링하면서 다음과 같은 의문이 들었다.
// 1번 방법 : 새로운 SecurityContext 생성
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(authToken);
SecurityContextHolder.setContext(securityContext);
// 2번 방법 : 기존 SecurityContext 사용
SecurityContextHolder.getContext().setAuthentication(authToken);
대부분의 JWT 검증 필터 구현 코드를 찾아보면, 2번 방법을 사용하는 것으로 보여진다. 또한, 우리 프로젝트에서는 1번 방법으로 구현을 했었지만, 2번 방법으로 변경해도 아무런 문제가 없이 동작한다.
여기서 드는 의문은,
어느 시점에
SecurityContextHolder
에SecurityContext
를 생성하고 넣어준 것인가?1번 방법이 아닌 2번 방법을 사용하는 이유는 뭘까?
결론
2번 방식을 통해 SecurityContextHolder.getContext() 를 호출하여 기존 SecurityContext를 사용하는 방식으로 진행하게 되었다.
왜 2번을 선택했고 근거에 대해서 하나씩 찾아보자.
필터 체인의 동작
필터 체인이 어떻게 동작하는지 파악하고, 어디서 SecurityContext가 생성되는지 파악을 하는 것이 우선이라고 판단했다. 필터 체인이 어떻게 구성이 되어있는지 파악하기 위해 @EnableWebSecurity(debug = true)
을 SecurityConfig 클래스에 설정해준 후 확인해보자.
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
public class SecurityConfig {
// ...
}
Filter Chain 동작
Security filter chain: [
DisableEncodeUrlFilter
WebAsyncManagerIntegrationFilter
SecurityContextHolderFilter // (1)
HeaderWriterFilter
CorsFilter
LogoutFilter
OAuth2AuthorizationRequestRedirectFilter
OAuth2LoginAuthenticationFilter
JwtAuthenticationFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
AuthorizationFilter
]
여기서 우리가 주목해야 하는 필터는 (1) SecurityContextHolderFilter 이다. 해당 필터는 이름에서 알 수 있듯이 SecurityContextHolder 와 관련된 작업을 하는 필터이다.
SecurityContextHolderFilter
필터의 내부 구조를 파악해보자.
필터의 doFilter 메서드
@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);
// 1. Context Load
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
// 2. Set Context
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
}
finally {
this.securityContextHolderStrategy.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
위 코드에서 집중해서 봐야 할 부분은
securityContextRepository
에서 컨텍스트를 불러오고securityContextHolderStrategy
를 통해 컨텍스트를 저장
하는 부분이다.
SecurityContext 로딩 및 설정:
Supplier<SecurityContext> deferredContext = this.securityContextRepository.loadDeferredContext(request);
try {
this.securityContextHolderStrategy.setDeferredContext(deferredContext);
chain.doFilter(request, response);
}
현재 요청에 대한 SecurityContext를 로드합니다.
SecurityContextHolderStrategy를 사용하여 SecurityContext를 설정하며, 기본적으로 ThreadLocal을 사용합니다.
이후, chain.doFiler() 를 통해 다음 필터로 요청을 전달합니다.
이렇게 알 수 있듯이, SecurityContextHolderFilter 에서 미리 사용할 SecurityContext
를 생성하고 SecurityContextHolder 에서 들고있는 것을 알 수 있다.
그렇다면, 어떤 방식이 맞는 방식일까?
정답은 없는 것 같다.
하지만, 현 시점 우리 프로젝트에서는 JWT 검증 필터 이전까지 SecurityContext 에 접근해서 Authentication
정보를 넣는 작업을 하지 않고 있다. 즉, 초기의 상태를 유지하고 있는 상태로 JWT 검증 필터까지 오게 되는 것이다.
이러한 상황에서는 굳이 새로운 컨텍스트를 생성(SecurityContext)하고 SecurityContextHolder 에 넣어주는 작업은 불필요할 것이라고 판단이 된다.
물론, 현재 컨텍스트에 필요한 정보가 담겨져 있을 경우 (새로운 필터가 도입되는 등의 문제로) 원하지 않는 동작을 할 것이라고 판단이 된다.
Last updated
Was this helpful?