[Spring Security] Architecture

수깡·2024년 2월 26일

Spring Security

목록 보기
1/11

🌟 스프링 시큐리티

인증, 권한 부여 및 일반적인 공격에 대한 보호를 제공하는 프레임워크
스프링 시큐리티는 principal - credential 패턴을 이용하여 인증한다.

🌟 Filter

스프링 시큐리티는 서블릿 필터를 기반으로 서블릿을 지원.
클라이언트가 어플리케이션으로 요청을 전송하면, 컨테이너는 Servlet과 여러 Filter로 구성된 FilterChain을 만든다. 그리고 요청 URI path 기반으로 HttpServletRequest를 처리한다.
Spring MVC 애플리케이션에서의 Servlet은 DispatcherServlet을 이용한다.

단일 HttpServletRequest, response 처리는 최대 한개의 Servlet이 담당하지만, Filter는 그림과 같이 여러개를 사용할 수 있다.

  • Filter는 FilterChain 안에 있을 때 효력을 발휘한다.
  • Filter는 다운 스트림에 있는 나머지 Filter와 Servlet에만 영향을 준다. 위 그림에서 보면 Filter 1은 Filter2, Servlet에 영향을 주지만, Filter 2는 Servlet에만 영향을 준다. 따라서 필터의 실행 순서가 매우 중요하다.

💡DelegatingFilterProxy

DelegatingfilterProxy라는 Filter 구현체로 서블릿 컨테이너의 생명주기와 스프링의 ApplicationContext를 연결한다.

서블릿 컨테이너는 Filter를 등록할 수 있지만, 스프링이 정의하는 빈은 인식하지 못한다. 하지만 DelegatingFilterProxy표준 서블릿 컨테이너 메커니즘으로 등록할 수 있으면서도, 모든 처리를 Filter를 구현한 스프링 빈으로 위임해준다.

DelegatingFilterProxyApplicationContext에서 Bean Filter0을 찾아 실행한다.

💡FilterChainProxy

FilterCHainProxy는 스프링 시큐리티가 제공하는 특별한 Filter로, SecurityFilterChain을 통해 여러 Filter 인스턴스로 위임한다.Bean이기 때문에 보통 DelegatingFilterProxy로 감싸져있다.

💡SecurityFilterChain

FilterChainProxy가 요청에 사용할 스프링 시큐리티의 필터들을 선택할 때는, SecurityFilterCHain을 사용한다.

FilterChainProxy는 스프링 시큐리티의 중심점이자 시작점이기 때문에, 필수로 여겨지는 작업을 수행할 수 있다.

예를 들어 SecurityContext를 비워 메모리 릭을 방지하거나, HttpFirewall을 적용하여 공격을 방어할 수도 있다.

이 이미지에는 여러개의 securityFilterCHain이 있는데, 어떤 체인을 사용할지는 FilterChainProxy가 결정하고, 가장 먼저 매칭한 SecurityFilterChain을 실행한다.

  • /api/messages/ URL을 요청할 경우, /api/** 패턴과 제일 먼저 매칭되므로, 0번 체인이 실행된다.

SecurityFIlterChain은 고유하고 격리된 설정을 가질 수 있다. 어플리케이션의 특정 요청은 시큐리티가 무시하길 바란다면, SecurityFilterChain에 보안 Filter를 0개 설정하는 것도 가능하다.

💡Security Filters

보안 필터들은 securityFilterChain API를 사용하여 FilterChainProxy에 삽입된다. 인증, 권한 부여, 악용 방지 등 다양한 목적으로 사용될 수 있는데, 필터는 특정 순서로 실행된다. 예를 들어 인증을 수행하는 필터는 권한 부여를 수행하는 필터보다 먼저 호출되어야 한다.

@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();
    }

}

위 구성의 순서는 다음과 같다.

  • csrf Filter
  • UserNamePasswordAuthentication Filter (formLogin)
  • BasicAuthentication FIlter (httpBasic)
  • Authorization Filter (authorizeHttpRequests)

💡Handling Security Exceptions

ExceptionTranslationFilterAccessDeniedException을 해석하고 AuthenticationException을 HTTP응답으로 바꿔준다.

1. 먼저 ExceptionTranslationFilter는, FilterChain.doFilter를 호출해서 어플리케이션의 나머지 로직을 실행한다.

  1. 인증받지 않은 사용자거나 AuthenticationException이 발생한경우 인증을 시작한다.
    2-1. securityContextHolder를 비우고
    2-2. RequestCacheHttpServletRequest를 저장한다. 사용자 인증에 성공하면 RequestCach로 기존 요청 처리를 이어간다.
    2-3. AuthnticationEntryPoint는 클라이언트에 credential을 요청할 때 사용한다. 예를 들어 로그인 페이지로 리다이렉트하거나 인증헤더를 전송한다.

  2. 반대로 AccessDeniedException이라면 접근을 거부한다. 거절된 요청은 AccessDeniedHandler에서 처리한다.

애플리케이션이 AccessDeniedException 또는 AuthenticationException를 발생시키지 않으면  ExceptionTranslationFilter는 아무 작업도 수행하지 않는다.

ExceptionTranslationFilter 의 구현 방식

try {
	filterChain.doFilter(request, response); 
} catch (AccessDeniedException | AuthenticationException ex) {
	if (!authenticated || ex instanceof AuthenticationException) {
		startAuthentication(); 
	} else {
		accessDenied(); 
	}
}

출처: 스프링 시큐리티 공식 문서

0개의 댓글