[docs 인덱싱] Spring Security / Servlet Applications / Architecture

inho ha·2024년 8월 10일
0

docs 인덱싱

목록 보기
2/2

docs

https://docs.spring.io/spring-security/reference/servlet/architecture.html

HTTP request 핸들러의 계층

Client가 application에 request를 보내면 컨테이너는 FilterChain을 생성한다.
(FilterChain은 URI 기반으로 HttpServletRequest를 처리하는 Filter와 Servlet으로 구성되어있다.)

이 필터에서 인증, 권한부여 등 전처리 및 후처리 작업을 수행할 수 있다.

다만 이 필터를 등록하고 관리하는 서블릿 컨테이너는 SpringApplicationContext에서 관리하는 Bean에 대해 직접적으로 알지 못해서 Bean을 직접적으로 필터로 사용하지 못한다.

Bean을 필터로 사용하기 위해서는 서블릿 컨테이너와 SpringApplicationContext를 연결시켜줄 무언가가 필요하다.

DelegatingFilterProxy

DelegatingFilterProxy는 Bean 필터를 가져와서 사용한다.
DelegatingFilterProxy를 사용하면 Bean으로 필터를 사용할 수 있다.

FilterChainProxy

FilterChainProxy는 Spring Security에서 제공하는 Bean 필터이다.
Bean 필터이기 때문에 FilterChain에 직접 필터로 등록하지 못하고 DelegatingFilterProxy를 통해서 필터로 사용할 수 있다.

SecurityFilterChain

SecurityFilterChain는 Spring Security에서 제공하는 보안 관련 필터들이다.
이 필터 체인은 요청이 들어올 때 각 필터가 순차적으로 실행되며, 인증, 권한 부여, CSRF 보호 등의 작업을 수행한다.

DelegatingFilterProxy를 여러개 사용하여 Security Filter를 사용하는 대신 SecurityFilterChain을 사용하는 이유

  1. Security 관련된 필터들을 묶어서 FilterChainProxy 이라는 명확한 시작 지점을 제공하여 명확한 디버깅 포인트를 제공
  2. Security 관련된 필터들을 묶어서 Security 관련 필수 작업을 수행
  3. 단순 필터는 URI 기반으로 실행여부가 결정되는데, FilterChainProxy에서는 HttpServletRequest의 HTTP method, header, parameter 등을 고려하여 실행여부를 결정 가능

Security Filter

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated()
            )
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults());
        return http.build();
    }

}

위와 같은 방식으로 SecurityFilterChain을 구성할 수 있다.

  1. csrf 필터
  2. 인증 필터
  3. http 필터
  4. 폼로그인 필터

커스텀 Security Filter 추가

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        String tenantId = request.getHeader("X-Tenant-Id"); 
        boolean hasAccess = isUserAllowed(tenantId); 
        if (hasAccess) {
            filterChain.doFilter(request, response); 
            return;
        }
        throw new AccessDeniedException("Access denied"); 
    }

}

위와 같은 방식으로 커스텀 필터를 구현하고

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ...
        .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); 
    return http.build();
}

위와 같이 SecurityFilterChain 에 커스텀 필터를 추가할 수 있다.

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false);
    return registration;
}

커스텀 필터를 빈으로 등록하여 사용하려는 경우에는 해당 필터가 서블릿 컨테이너에 등록되어 중복이 발생할 수 있다.

중복을 방지하기 위해서는 위와 같이 FilterRegistrationBean를 사용하여 setEnabled(false)를 설정하여 해당 필터가 서블릿 컨테이너에 등록되는 것을 막아야한다.

ExceptionTranslationFilter

ExceptionTranslationFilter는 AccessDeniedException, AuthenticationException 이 두가지 예외에 대해서 HTTP responses로 변환 처리를 해줄 수 있다.

인증이 필요한 요청인데 인증이 되지 않은 상태거나, 인증 과정에서 문제가 발생한 경우 AuthenticationException가 발생한다.

이 경우 아래와 같은 순서로 인증을 시작한다.
1. SecurityContextHolder 를 비워서 이전의 설정된 인증 정보 제거
2. 인증 이후 원래 요청했던 리소스에 접근할 수 있도록 RequestCache에 HttpServletRequest 저장
3. AuthenticationEntryPoint으로 클라이언트에게 인증 요청

AccessDeniedException가 발생한 경우 AccessDeniedHandler를 호출하여 처리된다.

RequestCacheAwareFilter

@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
	HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
	requestCache.setMatchingRequestParameterName("continue");
	http
		// ...
		.requestCache((cache) -> cache
			.requestCache(requestCache)
		);
	return http.build();
}

위와 같이 HttpSessionRequestCache에 HttpServletRequest를 저장하도록 설정할 수 있다.
requestCache.setMatchingRequestParameterName("continue"); 으로 파라미터에 continue가 있는 경우에만 저장하도록 설정 가능하다.

@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    RequestCache nullRequestCache = new NullRequestCache();
    http
        // ...
        .requestCache((cache) -> cache
            .requestCache(nullRequestCache)
        );
    return http.build();
}

또 위와 같이 저장하지 않도록 설정할 수 있다.

Logging

logging.level.org.springframework.security=TRACE

spring security는 기본적으로 상세한 로깅을 하지 않는다.
401, 403 등 발생 원인을 로그를 통해서 자세히 확인하기 위해서는 위의 설정이 필요하다.

profile
inho ha / ian(swatchon) / iha(42seoul)

0개의 댓글