본문 바로가기
IT Study/SpringBoot

Spring Security(3)

by Irwin-Kr 2025. 4. 26.

드디어 Security Filter에 대해 알아볼 시간이다...

앞서 공부했던 Spring Security의 구조를 정리해 보자면,

 

단일 HTTP요청에 따른

Servlet Filter

 

실행 필터의 모든 동작은 Spring Bean에 위임하는

DelegatingFilterProxy

 

DelegatingFilterProxy 내부에서 동작하는

FilterChainProxy

 

요청에 따른 Spring Security Filter 요소 호출을 결정하는

SecurityFilterChain


 

앞서 총 4가지에 대해서 이야기 다루었으며,

최하위 요소인 Security Filter에 대해 알아보자.

 

Security Filter

 

 Security Filter는 SecurityFilterChain API와 함께 FilterChainProxy에 추가.

이 Filter들은 보호, 인증, 권한 부여 등과 같이 다수의 다른 목적을 위해서 사용.

Filter들은 적절한 시기에 호출되도록 특별한 순서로 실행을 보장.

 

인증하는 Filter는 권한 부여 전 호출.

Spring Security's Filter의 순서를 알 필요는 없지만,

적절한 순서를 알고싶으면 FilterOrderRegistration code를 확인하자.

 

Security Filter들은 대부분 명확하지 않은 HttpSecurity 요소를 사용한다.

 

예시로부터, Security 설정을 고려해보자.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

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

		return http.build();
	} 
}

 

위 설정의 순서에 따라서 Filter 순서는

 

1. CSRF 공격에 보호를 위해 CsrfFilter를 호출

2. AuthenticationFilter는 인증 요청으로 호출

3. AuthorizationFilter는 권한 부여 요청으로 호출

 

※ CSRF(Corss-Site Reuqest Forgery) : 사이트간 요청 위조


Security Filter 출력

요청에 대한 추가된 필터가

Security Filter 목록에 있는지 확인할때 사용하는데

Application이 시작할때 콘솔에서 DEBUG level 출력으로 확인 가능하다.

 

2025-04-24T09:33:10.651-00:00    DEBUG  15423  ---  [ ~.main] ~.DefaultSecurityFilterChain : Will secure any request with [ DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter, BasicAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter]

 

 

Security event 로그를 활용해

응용프로그램에 대한 다양한 설정을 할 수 있다.

 

1. 각각의 요청으로부터 개별 filter에 대한 호출을 출력하도록 응용프로그램을 설정

2. 추가한 filter 호출을 위해 특별한 요청이나 예외 확인

 

이외에도 여러 설정이 가능하다.

 


Filter Chain에 Filter 추가

 

기본 Security Filter들은 응용프로그램의 보안을 제공하는데 충분하지만

SecurityFilterChain에 사용자 지정 Filter를 추가하기도 한다.

 

아래는 필터를 추가하기 위한 3개의 methods와 설명이다.

 

1. addFilterBefore(Filter, Class<?>) : 다른 필터 에 추가

2. addFilterAfter(Filter, Class<?>) : 다른 필터 에 추가

3. addFilterAt(Filter, Class<?>) : 다른 필터와 교체

 

customFilter 추가

 

사용자 지정 필터를 만들때에는 Filter Chain 내부의 위치 결정이 필요하는데

아래와 같이 Filter Chain에서 일어나는 순서를 확인하고 고려하자.

 

1. SecurityContext는 session으로부터 적재

2. 요청에 CORS, CSRF등의 일반적 공격의 보호

3. 요청 인증

4. 요청에 대한 권한 부여

 

 

대부분의 사용자 지정 Filter는 LogoutFilter 뒤에 위치하게 되는데,

tenant id header 필터를 추가하고 현재 사용자는 tenant에 접근을 확인하는 경우와 같다.

Filter Filter의 위치(뒤)
보호 SecurityContextHolderFilter
인증 LogoutFilter
권한 부여 AnonymousAuthenticationFilter

 

 

 

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"); // 1. 요청 header로 부터 tenant Id를 가져온다.
        boolean hasAccess = isUserAllowed(tenantId); // 2. 현재 사용자가 tenant id로 접근하였는지 확인
        if(hasAccess){ // 3. 사용자 접근 유무 확인
            filterChain.doFilter(request, response); // 4-1. chain에서 남은 filter를 호출
            return;
        }
        throw new AccessDeniedException("Access denied"); // 4-2. 사용자 접근이 아닌 경우 오류 발생
        
    }
}

 

필터 구현 대신에 OncePerRequestFilter에서 확장과 doFilterInternal 메소드를 이용한다.

 

 

사용자 정의 Filter를 적용하려면 위의 설명을 기반으로

권한을 부여하는 Filter인 AnonymousAuthenticationFilter 후에 추가한다.

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
	http
    	// ...
        /* AnonymousAuthenticationFilter 후에 TenantFilter를 추가 */
        .addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class);
        
        
        return http.build();
}

 

Filter Chain에서 TenantFilter가 호출되고

사용자가 Tenant ID로 접속하는지 확인한다.

 

 

Bean으로서 Filter 선언

Spring bean으로 Filter를 선언하고 싶으면,

@Component나 다른 Annotation으로 선언하여

Spring Boot가 내장 container에 자동으로 등록하도록 한다.

 

 

이로 인하여 Filter가 container와 Spring Security 각 한번씩과

다른 순서로 두번 호출이 되어 종종 Spring bean이 아니게 된다.

 

그렇지만 Filter가 Spring bean(의존주입의 활용을 위해)으로 필요한 경우

FilterRegisterationBean bean 선언과 속성 활성화를 피하여

Spring Boot에 등록하지 않도록 한다.

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

 

이를 통해 HttpSecurity에만 추가한다.

 

Spring Security Filter 사용자 정의

 

FIlter의 DSL 방식으로 사용해 Spinrg Security의 Filter 구성한다.

예로, BasicAuthenticationFilter을 추가하는 간단한 방법은 DSL에 요청하는 것이다.

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
	http.httpBasic(Customizer.withDefaults())
    // ...
    return http.build();
}

 

 

직접 Spring Security Filter 구성할때, addFilterAt과 같이 명시한다.

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
	BasicAuthenticationFilter basic = new BasicAuthenticationFilter():
    // ... configure
    http
    	// ...
        .addFilterAt(basic, BasicAuthenticationFilter.class);
        
        return http.build();
}

 

 

Filter가 이미 bean에 추가되었으면, Spring Security는 오류를 발생한다.

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
    BasicAuthenticationFilter basic = new BasicAuthenticationFilter(); // BasicAuthenticationFilter 추가
    // ... configure
    
    http.httpBasic(Customizer.withDefaults()) // HttpSecurity에서 BasicAuthenticationFilter 재추가
        // .. BasicAuthenticationFiltet는 두번 추가되었음.
        .addFilterAt(basic, BasicAuthenticationFilter.class);
    
    return http.build();
}

 

이런 경우에 BasicAuthenticationFilter을 직접 구성하므로,

httpBasic의 호출을 제거한다.

 

특정 Filter를 추가하지 않도록 HttpSecurity를 재구성 할수 없는 경우

DSL의 method를 비활성화하여 Spring Security Filter를 비활성화 할수 있다.

.httpBasic((basic) -> basic.disable())

 

 

이젠...Security의 예외처리 방법에 대해 알아볼 것이다..

'IT Study > SpringBoot' 카테고리의 다른 글

Spring Security 인증 구조 - 2  (0) 2025.06.28
Servlet 인증 구조  (2) 2025.06.14
Spring Security(4)  (1) 2025.05.09
Spring Security (2)  (0) 2025.04.11
Spring Security (1)  (0) 2025.03.28