들어가기
밑에서 설명하겠지만 스프링 빈들은 Spring Container에서 관리되기때문에 Spring Container 바깥에서는 자유롭게 접근할 수 없다.
대표적으로 Servlet Container(필터가 존재하는 곳)는 Spring Container에 접근할 수 없는데,,,
왜 굳이 불편하게 접근이 어려운 곳에서 인증과 인가를 처리하는 지 의문이였다.
학습 후 내가 내린 결론은 불편하긴 하지만 서버 깊숙히 들어오기 전(필터 단에서) 모든 인증 인가 처리를 하기위해서라고 생각된다.
Servlet Container
흔히 볼 수 있는 클라이언트와 서버간의 통신을 요약해 놓은 그림이다.
스프링부트는 톰캣(WAS)를 내장하고 있기 때문에 스프링을 실행시키면 자동으로 톰캣이 실행된다.
톰캣은 요청을 사용하기 편하게 전처리를 거쳐 Request
Response 객체를 생성해 Filter 인터페이스를 구현한 객체에게 전달한다.
아래는 Filter를 구현한 예시다.
import javax.servlet.*;
import java.io.IOException;
public class GumpFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 초기화 하기 위해서 사용 전 호출하는 메소드
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 처리 하기 전 로직
chain.doFilter(request, response);
// 처리 하고 나서 로직
}
@Override
public void destroy() {
// 필터 종료
}
}
DelegatingFilterProxy
위에서 언급한 Request Reponse 객체는 서블릿 컨테이너가 가지는 필터 순서에 따라서chain.doFilter(request,response) 으로 다음 필터를 호출한다.
필터를 거치다보면 DelegatingFilterProxy를 만나게 되는데 이 친구가 정말 중요한 친구다.
그림을 보면 Tomcat 영역과 Spring container 영역은 완전히 분리돼있다. 그렇기 때문에 필터에서 스프링 빈에 접근할 수 없다.
하지만 DelegatingFilterProxy를 이용하면 필터에서 스프링 빈에 접근할 수 있다!
한 마디로 오작교 역할을 해준다.
DelegatingFilterProxy는 GenericFilterBean을 구현하고 있는데 GenericFilterBean이 Filter를 구현하고있기 때문에
결국 DelegatingFilterProxy도 필터로서 동작하게된다. (GenericFilterBean에 대해서는 뒤에서 알아본다.)
DelegatingFilterProxy는 SpringSecurityFilter에게 요청을 위임한다.
SecurityFilterChain은 기본적으로 등록된 필터들이 존재하고 커스텀하게 필터를 추가할 수도 있다.
DelegatingFilterProxy Deep Dive [어떻게 위임 절차를 수행할까?]
DelegatingFilterProxy의 생성자에 브레이크포인트를 걸어보면 스프링부트가 뜨면서 생성자에 브레이크가 걸린다.
즉 스프링 서버가 떠오르면서 초기화되는 클래스다.
생성자에 targetBeanName 과 wac(WebApplicationContext) 를 파라미터로 받고있다.
먼저 targetBeanName은 "springSecurityFilterChain"이라고 넘어왔다.
targetBeanName은 DelegatingFilterProxyRegistrationBean 클래스를 타고 SecurityFilterAutoConfiguration클래스를 타고...
아래 클래스에 하드코딩 돼 있다.
public abstract class AbstractSecurityWebApplicationInitializer implements WebApplicationInitializer {
private static final String SERVLET_CONTEXT_PREFIX = "org.springframework.web.servlet.FrameworkServlet.CONTEXT.";
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
private final Class<?>[] configurationClasses;
// 중략
도대체 targetBeanName이 무엇 이길래 DelegatingFilterProxy가 넘겨 받아야하는 지 알아보자.
사실 targetBeanName는 WebSecurityConfiguration 클래스에서 정의하는 빈의 이름이다.(이미 변수명이 targetBeanName이라 눈치는 쉽게 챌 수 있다.)
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
if (!hasFilterChain) {
this.webSecurity.addSecurityFilterChainBuilder(() -> {
this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
this.httpSecurity.formLogin(Customizer.withDefaults());
this.httpSecurity.httpBasic(Customizer.withDefaults());
return this.httpSecurity.build();
});
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
이 클래스는 스프링의 필터들을 securityFilterChains라는 이름으로 비어있는 리스트 형식으로 초기화돼있다.
만약 필터체인이 비어있다면 기본적 설정을 수행하고 비어있지 않다면 필터들을 하나하나 등록해준다.
스프링 시큐리티 설정 파일에 @EnableWebSecurity 라는 어노테이션을 통해 시큐리티를 활성화할텐데,
위 어노테이션으로 WebSecurityConfiguration 을 import 하고 securityFilterChain 이라는 이름을 알수 있다.
이제 다음으로 wac(WebApplicationContext)를 알아보자.
WebApplicationContext는 ApplicationContext를 상속한 클래스다.
ApplicationContext는 흔히들 부르는 스프링 빈을 관리하는 스프링 컨테이너라고 보면 된다.(BeanFactory의 하위 클래스)
WebApplicationContext는 이 스프링 컨테이너 기능과 SevletContext에 접근할 수 있는 기능이 추가된 클래스다.
DelegatingFilterProxy는 아래 처럼 WebApplicationContext에 접근해 springSecurityFilterChain라는 이름을 찾아온다.
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
// springSecurityFilterChain을 불러옴
Filter delegate = wac.getBean(targetBeanName, Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
'Spring' 카테고리의 다른 글
회원 도메인 객체 코드 개선하기 (0) | 2024.04.12 |
---|---|
@Value 감옥에서 벗어나기 [@ConfigurationProperties] (0) | 2024.04.03 |
[Spring Security JWT 로그인 (1)] 어떤 로그인 방식이 좋을까 (1) (0) | 2024.03.11 |
[Spring Security JWT 로그인 (2)] JWT가 탈취된다면 어떻게 대응해야 할까 (0) | 2024.03.10 |
Redis Session Clustering [세션 고정] (0) | 2023.12.17 |