Spring/Spring Security

[Spring Security] 1. Architecture

noahkim_ 2021. 7. 28. 11:01

1. A Review Of Filters

 

  • Spring Security는 Servlet Filter 기반으로 동작함

 

서블릿 동작 원리

  1. 클라이언트가 요청을 보냄
  2. 서블릿 컨테이너: 요청 경로를 기준으로 어떤 Filter들이 요청을 처리할지 결정.
  3. 서블릿 컨테이너: 필터들을 묶어 FilterChain 생성

 

Servlet Filter

  • HTTP 요청을 처리하기 위한 사전/사후 작업용 인터셉터

 

동작
구분 설명 결과
stop 이후의 필터나 서블릿에 요청을 전달하지 않음
Filter가 직접 응답 작성
modify HttpServletRequest 또는 HttpServletResponse를 수정한 뒤 전달
수정된 객체가 하위로 전달됨

 

FilterChain

  • 여러 개의 Servlet Filter가 연결된 객체
  • 요청을 다음 필터로 넘길지 결정할 수 있음

 

2. DelegatingFilterProxy

  • 서블릿 컨테이너의 생명주기와 Spring의 ApplicationContext 사이를 연결해주는 Servlet Filter 구현체
  • 서블릿 컨테이너는 Spring Filter를 인식하지 못함 → 이를 해결하기 위해 사용

 

동작 방식

  1. 서블릿 컨테이너에 DelegatingFilterProxy를 서블릿 필터로 등록
  2. DelegatingFilterProxy는 내부적으로 Spring Bean으로 등록된 FilterChainProxy를 찾아 위임함
    • FilterChainProxy은 springSecurityFilterChain 이란 이름으로 등록되어 있음

 

장점

장점 설명
Spring Bean 필터 사용 가능 DelegatingFilterProxy를 통해 Spring Bean으로 등록된 필터를 사용할 수 있음
지연 로딩 지원 Spring의 ContextLoaderListener는 컨테이너가 시작되고 나서야 ApplicationContext를 로딩함
→ DelegatingFilterProxy는 실제 필터를 나중에 로딩하므로 이 타이밍 문제를 해결할 수 있음

 

3. FilterChainProxy

  • Spring Security가 사용하는 메인 필터 (중심 진입점)
  • 여러 SecurityFilterChain 중, 요청에 적절한 SecurityFilterChain에 라우팅하여 처리

 

4. SecurityFilterChain

  • 요청에 적용할 보안 필터들의 묶음
  • HttpSecurity DSL로 구성됨
  • 이 필터체인은 모두 FilterChainProxy에 등록되어 관리됨

 

RequestMatcher

  • HTTP 요청이 특정 조건에 부합하는지 판단하는 인터페이스
  • SecurityFilterChain 설정 시, 특정 요청에 보안 설정을 적용할지 여부를 결정할 때 사용됨
구현체 클래스 설명 사용 예시
AntPathRequestMatcher ANT 스타일 경로 매칭 (/api/**, /admin/*)
new AntPathRequestMatcher("/admin/**")
MvcRequestMatcher Spring MVC 경로 방식으로 매칭
new MvcRequestMatcher(handlerMappingIntrospector, "/admin")
RegexRequestMatcher 정규표현식 기반 매칭
new RegexRequestMatcher("/admin/.*", "GET")
OrRequestMatcher 여러 RequestMatcher 중 하나라도 일치하면 true
new OrRequestMatcher(matcher1, matcher2)
AndRequestMatcher 여러 RequestMatcher가 모두 일치해야 true
new AndRequestMatcher(matcher1, matcher2)
NegatedRequestMatcher 특정 Matcher의 결과를 반전 (not)
new NegatedRequestMatcher(matcher)

 

 

예시

더보기

기본 URL

http
  .securityMatcher("/admin/**") // URL 패턴만 기준
  .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

 

HTTP 메서드

http
  .securityMatcher(new AntPathRequestMatcher("/api/**", "POST")) // POST 요청만 매칭
  .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

 

커스텀 RequestMatcher (헤더 기반)

http
  .securityMatcher(request -> {
      String header = request.getHeader("X-Tenant-Id");
      return header != null && header.equals("tenant-123");
  })
  .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

 

요청 파라미터

http
  .securityMatcher(request -> "true".equals(request.getParameter("secure")))
  .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

 

SecurityFilterChain 정의

설정

더보기
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults())
            .httpBasic(Customizer.withDefaults())
            .formLogin(Customizer.withDefaults())
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated());

        return http.build();
    }
}
필터 이름 DSL 방식
CsrfFilter http.csrf()
BasicAuthenticationFilter http.httpBasic()
UsernamePasswordAuthenticationFilter http.formLogin()
AuthorizationFilter
http.authorizeHttpRequests()

 

기본 필터 구성

더보기
DisableEncodeUrlFilter,
WebAsyncManagerIntegrationFilter,
SecurityContextHolderFilter,
HeaderWriterFilter,
CorsFilter,
CsrfFilter,
LogoutFilter,
BasicAuthenticationFilter,
RequestCacheAwareFilter,
SecurityContextHolderAwareRequestFilter,
AnonymousAuthenticationFilter,
ExceptionTranslationFilter,
AuthorizationFilter

 

필터 설명
DisableEncodeUrlFilter
URL에 세션 ID를 붙이는 기능 제거
WebAsyncManagerIntegrationFilter
비동기 요청 처리 시 SecurityContext 전달
SecurityContextHolderFilter
SecurityContext 저장 및 정리
HeaderWriterFilter
X-Frame-Options 등 보안 헤더 설정
CorsFilter
CORS 처리 (pre-flight 요청 등)
CsrfFilter CSRF 토큰 검증
LogoutFilter
로그아웃 URL 요청 처리
RequestCacheAwareFilter
인증 후 원래 요청으로 리다이렉트
SecurityContextHolderAwareRequestFilter
HttpServletRequest 확장 (getUserPrincipal 등 지원)
AnonymousAuthenticationFilter
인증되지 않은 사용자를 익명 사용자로 처리
ExceptionTranslationFilter
인증/인가 실패 시 예외를 처리하고 리다이렉트 또는 응답 전환
AuthorizationFilter
인가(Authorization) 검사 수행

 

여러 개의 SecurityFilterChain 매칭

  • 여러개가 매칭되어도 첫 번쨰 매칭 체인만 실행됨

 

5. Security Filters

종류

역할 구분 설명 대표 필터 예시
Exploit 보호 웹 기반 공격 방지 (CSRF, CORS, 클릭재킹, HSTS 등)
CsrfFilter
HeaderWriterFilter
CorsFilter
인증 사용자의 신원을 확인하고 SecurityContext에 인증 정보 저장
UsernamePasswordAuthenticationFilter
BasicAuthenticationFilter
인가 인증된 사용자가 요청한 리소스에 접근할 수 있는지 권한 확인 AuthorizationFilter
(AccessDecisionManager와 연결됨)

 

Adding a Custom Filter

추가 방식
메서드 설명
addFilterBefore()
지정 필터 이전에 추가
addFilterAfter()
지정 필터 이후에 추가
addFilterAt() 지정 필터 대체

 

필터 위치 정하기
필터 목적 이 필터 뒤에 추가 이유
Exploit 보호 필터 SecurityContextHolderFilter
컨텍스트 로드 후 실행해야 하므로
인증 필터 LogoutFilter
보안 컨텍스트 + 보호 이후
인가 필터 AnonymousAuthenticationFilter
인증까지 끝난 뒤에 실행해야 함

 

Bean으로 필터 등록 시 주의사항

  • 빈으로 등록된 필터는 자동으로 Servlet Filter로도 등록됨
  • SecurityFilterChain에 추가할 경우 중복 실행됨
  • Servlet 등록을 막음으로써 중복 실행을 해결할 수 있음

 

예제) Servlet 등록 막기

더보기
@Bean
public FilterRegistrationBean<CustomFilter> customFilterRegistration(CustomFilter filter) {
    FilterRegistrationBean<CustomFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false); // 서블릿 컨테이너 등록 방지
    return registration;
}

 

Customizing a Spring Security Filter

예) 수동 등록

더보기
BasicAuthenticationFilter basic = new BasicAuthenticationFilter(...);
http.addFilterAt(basic, BasicAuthenticationFilter.class);

 

예) 비활성화

더보기
http.httpBasic(basic -> basic.disable()); // 필터 비활성화

 

6. Handling Security Exceptions

ExceptionTranslationFilter

  • 인증, 인가 예외를 처리하기 위한 전용 필터
예외 유형 의미 예외 처리
AuthenticationException 인증되지 않은 사용자가 보호된 리소스에 접근하려 할 때 발생
SecurityContextHolder 비움
Request 저장 (재시도용)
AuthenticationEntryPoint 호출
- 리다이렉션 (로그인 페이지 등)
- 401 응답코드 반환
AccessDeniedException 인증은 되었지만 권한이 없는 사용자가 접근할 때 발생
AccessDeniedHandler 호출
- 403 응답코드 반환

 

7. Saving Requests Between Authentication

  • 인증되지 않은 사용자가 로그인해야 접근 가능한 페이지를 요청했을 때, 
  • 로그인 후 다시 그 요청으로 돌아가기 위해 요청을 임시 저장해둬야 함

 

RequestCacheAwareFilter

  • 인증 성공 후, 저장된 요청을 RequestCache에서 꺼내 재시도하는 필터
  • 로그인 성공 시, 원래 요청을 꺼내 처리함

 

RequestCache

  • 인증 필요 요청을 저장하는 인터페이스 (재시도 용)
구현체 의미
HttpSessionRequestCache 기본 구현체
NullRequestCache 저장을 원하지 않을 경우 사용하는 구현체

 

ex) 특정 파라미터가 있을때만 요청 저장

더보기
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
    requestCache.setMatchingRequestParameterName("continue");

    http.requestCache(cache -> cache.requestCache(requestCache));
    return http.build();
}

 

ex) 저장 안하기

더보기
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    http.requestCache(cache -> cache.requestCache(new NullRequestCache()));
    return http.build();
}

 

8. Logging

  • 필터 체인 로그를 제공함
로그 레벨 확인 내용 사용 목적
DEBUG 어떤 필터들이 전체적으로 등록되어 있는지 애플리케이션 시작 시, SecurityFilterChain 구성 확인
TRACE 특정 요청에 대해 각 필터가 언제 실행되었는지 요청 처리 과정에서 어느 필터가 실행됐고 어디서 차단됐는지 확인

 

 

참고