[Spring] Spring Security

2026. 5. 30. 22:00·Framework/Spring

 

안녕하세요.

오늘은 Spring Security의 전체 개념과, 내부적으로 가장 핵심이 되는 FilterChain의 동작 원리를 정리해보려고 합니다.

설정 코드보다 "어떻게 동작하는가" 에 초점을 맞춥니다. 동작 원리를 모르면 설정을 외워도 디버깅이 어렵기 때문입니다.

 

 

인증(Authentication)과 인가(Authorization)

웹 애플리케이션의 보안은 결국 두 가지 질문에 답하는 일입니다.

구분 영문 질문 예시
인증 Authentication "당신은 누구입니까?" 로그인
인가 Authorization "이 작업을 할 수 있습니까?" 관리자 페이지 접근

이 두 개념은 보안 처리의 출발점이지만, 이 글에서는 인증/인가 자체보다 "보안 처리가 요청 흐름 어디에 끼어드는가" 에 집중합니다.

 

 

Spring Security 이전의 보안 처리

Spring Security가 없을 때는 보통 Servlet Filter에서 직접 보안을 처리해야 했습니다.

public class AuthFilter implements Filter {
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 1. 인증 확인
        HttpSession session = request.getSession(false);

        if (session == null || session.getAttribute("user") == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            
            return;
        }

        // 2. 권한 확인
        User user = (User) session.getAttribute("user");

        if (request.getRequestURI().startsWith("/admin") && !user.hasRole("ADMIN")) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            
            return;
        }

        // 3. CSRF 검증
        if (isMutationMethod(request) && !isValidCsrfToken(request)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            
            return;
        }

        // 4. 보안 헤더 설정
        response.setHeader("X-Frame-Options", "DENY");

        chain.doFilter(req, res);
    }
}

문제는 보안 책임이 한 곳에 몰린다는 점입니다.

  • 인증 / 인가 / CSRF / 세션 / 헤더 처리 혼재
  • OAuth2, Remember-Me 등을 직접 구현해야 함
  • 인증 객체를 요청 전체에 전파하기 어려움
  • 실수 하나가 곧 보안 취약점으로 이어짐

보안은 실수 비용이 큰 영역이라, 직접 구현할수록 위험해집니다.

 

 

Spring Security란?

Spring Security는 웹 보안을 선언적이고 표준화된 방식으로 처리할 수 있게 해주는 프레임워크입니다.

핵심 아이디어는 다음과 같습니다.

  • 모든 요청을 가로채는 보안 Filter Chain 제공
  • 보안 책임을 작은 필터들로 분리
  • 인증 / 인가 / 세션 / CSRF 등을 설정 기반으로 선언

의존성만 추가해도 기본 보안이 자동 적용됩니다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

자동으로 적용되는 대표 기능은 다음과 같습니다.

  • 모든 요청 인증 필요
  • 기본 로그인 페이지 제공
  • CSRF 보호 활성화
  • 세션 고정(Session Fixation) 방어
  • 보안 헤더 자동 설정

설정 없이도 운영 가능한 수준의 기본 보안이 적용됩니다.

@Transactional이 트랜잭션 관리의 선언적 처리를 제공했다면, Spring Security는 보안 처리의 선언적 처리를 제공합니다.

 

 

왜 Filter 기반으로 동작할까?

Spring Security가 Filter 기반으로 동작하는 이유는 보안이 Controller 이전 단계에서 처리되어야 하기 때문입니다.

인증되지 않은 요청이 Controller까지 도달한 뒤 차단되는 것이 아니라 아래 흐름으로 동작해야 애플리케이션 전체를 일관되게 보호할 수 있습니다.

요청
  ↓
Filter (인증/인가 검사)
  ↓ 통과
Controller

즉, Spring Security는 DispatcherServlet 이전 단계에서 요청을 먼저 검사합니다.

 

 

Servlet Filter Chain

@Transactional의 핵심이 AOP 프록시라면, Spring Security의 핵심은 Servlet Filter Chain입니다.

겉으로는 설정 기반으로 동작하지만, 내부에서는 여러 보안 필터가 순서대로 실행됩니다.

요청 흐름은 다음과 같습니다.

Client 요청
   ↓
Servlet Container
   ↓
Servlet Filter Chain
   ├─ CharacterEncodingFilter
   ├─ DelegatingFilterProxy  ← Spring Security 시작 지점
   └─ ...
   ↓
DispatcherServlet
   ↓
Controller

중요한 점은 Spring Security가 자신의 모든 필터를 Servlet Container에 직접 등록하지 않는다는 것입니다.

실제로 등록되는 것은 DelegatingFilterProxy 필터 하나뿐이고, 내부적으로 Spring Bean인 보안 필터 체인으로 처리를 위임합니다.

이렇게 하는 이유는 보안 필터들도 Spring Bean으로 관리하여 아래 Spring의 기능을 그대로 활용하기 위해서입니다.

  • DI(의존성 주입)
  • Bean Lifecycle
  • AOP
  • 설정 기반 조립

 

 

세 가지 핵심 컴포넌트

Spring Security 내부 구조를 이해하려면 아래 세 가지를 구분해야 합니다.

컴포넌트 역할
DelegatingFilterProxy Servlet Container에 등록되는 단일 Filter
FilterChainProxy 여러 SecurityFilterChain을 관리
SecurityFilterChain 실제 보안 필터들의 순서 있는 모음

호출 흐름은 다음과 같습니다.

Servlet Container
    ↓
DelegatingFilterProxy
    ↓
FilterChainProxy
    ↓
SecurityFilterChain
    ↓
Filter 1 → Filter 2 → Filter 3 ...
    ↓
DispatcherServlet
    ↓
Controller

여기서 가장 많이 헷갈리는 부분은 FilterChainProxy와 SecurityFilterChain의 차이입니다.

  • FilterChainProxy → 여러 SecurityFilterChain을 관리하는 관리자
  • SecurityFilterChain → 실제 보안 필터들이 들어있는 체인

즉, 앱에는 FilterChainProxy가 하나 존재하고, SecurityFilterChain은 여러 개 존재할 수 있습니다.

 

 

주요 보안 필터 실행 순서

기본 설정에서 자주 마주치는 주요 필터는 다음과 같습니다.

# 필터 역할
1 SecurityContextPersistenceFilter 요청 시작/종료 시 SecurityContext 로드/저장
2 LogoutFilter 로그아웃 처리
3 UsernamePasswordAuthenticationFilter Form Login 인증 처리
4 BasicAuthenticationFilter HTTP Basic 인증 처리
5 RequestCacheAwareFilter 로그인 후 원래 요청 복원
6 SecurityContextHolderAwareRequestFilter Request 객체 보안 기능 확장
7 AnonymousAuthenticationFilter 익명 사용자 처리
8 SessionManagementFilter 세션 관리
9 ExceptionTranslationFilter 인증/인가 예외를 HTTP 응답으로 변환
10 FilterSecurityInterceptor 요청 URL에 대한 최종 인가 결정

참고: AuthorizationFilter는 Spring Security 5.5, SecurityContextHolderFilter는 5.7에서 도입되어 기존 필터의 후속으로 사용할 수 있게 되었고, 6.0에서 기본 동작이 이쪽으로 변경되었습니다. 본 글은 Spring Boot 2.7.6 기본값(이전 필터) 기준으로 설명합니다.

특히 아래 두 개는 매우 중요합니다.

ExceptionTranslationFilter

뒤쪽 필터에서 발생한 예외를 HTTP 응답으로 변환합니다.

  • 인증 실패 → 로그인 페이지 이동
  • 권한 부족 → 403 Forbidden

즉, Security 예외를 사용자 응답으로 번역하는 역할입니다.

FilterSecurityInterceptor

실제 URL 접근 권한을 최종 판단합니다. /admin/** 접근 시 현재 사용자가 ROLE_ADMIN을 보유했는지 같은 검사를 수행합니다.

 

 

SecurityContext는 어디 저장될까?

Spring Security는 인증 정보를 SecurityContextHolder에 저장합니다.

기본적으로 ThreadLocal 기반으로 동작하기 때문에, 같은 요청 흐름 안에서는 어디서든 현재 인증 정보를 조회할 수 있습니다.

Authentication auth = SecurityContextHolder.getContext().getAuthentication();

이 구조 덕분에 Controller, Service 어디서든 로그인 사용자 정보를 쉽게 사용할 수 있습니다.

단, @Async나 별도 스레드 풀에서는 ThreadLocal이 전파되지 않으므로, 비동기 처리 시에는 DelegatingSecurityContextExecutor 등을 통해 컨텍스트를 명시적으로 전달해야 합니다.

 

 

디버깅 시 가장 먼저 보는 로그

Spring Security 디버깅의 시작점은 로그입니다.

logging:
  level:
    org.springframework.security: DEBUG

로그를 켜면 아래를 모두 확인할 수 있습니다.

  • 어떤 SecurityFilterChain이 선택됐는지
  • 어떤 필터가 실행됐는지
  • 인증 객체가 어떻게 변경됐는지

보안 이슈는 대부분 "어떤 필터가 실행됐는가?" 를 보면 풀립니다.

 

 

SecurityFilterChain은 여러 개 둘 수 있다

하나의 애플리케이션에서 서로 다른 보안 정책이 필요한 경우가 많습니다.

  • /api/** : JWT 인증, Stateless, CSRF OFF
  • 일반 웹 : Session 기반 로그인, CSRF ON

이 경우 SecurityFilterChain을 여러 개 둘 수 있습니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    @Order(1)
    public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        http
            .securityMatcher("/api/**")
            .csrf(csrf -> csrf.disable())
            .sessionManagement(s -> s
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            );

        return http.build();
    }

    @Bean
    @Order(2)
    public SecurityFilterChain webSecurity(HttpSecurity http)
            throws Exception {

        http
            .authorizeHttpRequests(auth -> auth
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .formLogin(Customizer.withDefaults());

        return http.build();
    }
}

FilterChainProxy는 @Order 순서대로 검사해서 가장 먼저 매칭되는 체인 하나만 선택해 사용합니다.

즉, 첫 번째로 매칭된 체인만 사용됩니다.

 

 

securityMatcher vs requestMatchers

실무에서 가장 많이 헷갈리는 부분입니다. 둘은 역할이 다릅니다.

메서드 역할
securityMatcher() 어떤 SecurityFilterChain 자체를 선택할지 결정
requestMatchers() 선택된 체인 내부에서 권한 정책 적용

예를 들어 .securityMatcher("/api/**")는 "이 요청을 어떤 보안 체인이 처리할까?" 를 결정합니다.

반면 .requestMatchers("/admin/**").hasRole("ADMIN")은 "선택된 체인 안에서 어떤 권한을 요구할까?" 를 결정합니다.

이 둘을 혼동하면 의도와 다른 보안 정책이 적용되기 쉽습니다.

참고: requestMatchers는 Spring Security 5.8에서 도입된 메서드로, 5.7.x 환경에서는 antMatchers / mvcMatchers / regexMatchers를 사용합니다. 6.0부터는 antMatchers 계열이 제거되고 requestMatchers로 통일됩니다.

 

 

Spring Security의 특징

특징 설명
선언적 설정 설정 기반으로 보안 정책 정의
Filter Chain 기반 보안 책임을 작은 필터로 분리
표준 보안 기능 제공 인증, 인가, CSRF, OAuth2 등
높은 확장성 커스텀 Filter / Provider 추가 가능
SecurityContext 자동 관리 인증 정보 자동 전파

가장 중요한 특징은 "보안을 Filter들의 조합으로 처리한다" 는 점입니다.

 

 

Spring Security의 장점

장점 설명
검증된 보안 기본값 기본 보안 설정 자동 적용
책임 분리 인증/인가/예외 처리 등이 필터 단위로 분리
풍부한 확장 포인트 다양한 인증 방식 확장 가능
표준 보안 자동화 CSRF/OAuth2 등을 직접 구현할 필요 없음
뛰어난 추적 가능성 로그 기반 디버깅 가능

보안에서 가장 위험한 부분은 "기본기를 놓치는 것"인데, Spring Security는 그 부분을 프레임워크 차원에서 해결해줍니다.

 

 

Spring Security의 단점

단점 설명
높은 학습 난이도 Filter 구조 이해 필요
설정 추적 어려움 DSL이 내부적으로 어떤 Filter를 추가하는지 파악 어려움
버전별 변화 큼 5.x → 6.x 변경 사항 많음
자동 설정의 함정 CSRF 등 기본값이 의도와 다르게 느껴질 수 있음
깊은 호출 스택 디버깅 시 복잡함

다만 이는 프레임워크의 결함이라기보다 "동작 원리를 모르면 의도와 다르게 동작하기 쉬운 특성" 에 가깝습니다.

@Transactional이 프록시 기반 동작을 이해해야 제대로 사용할 수 있는 것과 비슷합니다.

 

 

마무리 정리

이번 글에서는 Spring Security의 개념과 FilterChain 동작 원리를 정리했습니다.

핵심을 다시 요약하면 다음과 같습니다.

  • Spring Security는 Servlet Filter 기반 보안 프레임워크다.
  • 실제 핵심은 FilterChainProxy와 SecurityFilterChain이다.
  • 요청은 여러 보안 필터를 순서대로 통과한다.
  • 인증 정보는 SecurityContextHolder(ThreadLocal)에 저장된다.
  • 디버깅의 핵심은 어떤 SecurityFilterChain이 선택됐고, 어떤 Filter가 실행됐는가를 확인하는 것이다.

@Transactional의 핵심이 프록시 기반 동작이었다면, Spring Security의 핵심은 FilterChain 기반 동작입니다.

이 흐름만 제대로 이해해도 실무에서 발생하는 대부분의 보안 이슈를 훨씬 빠르게 진단할 수 있습니다.

 

읽어주셔서 감사합니다.

 

본 글은 Spring Boot 2.7.6 (Spring Security 5.7.x) 기준입니다.

 

 

참조

  • https://docs.spring.io/spring-security/reference/5.7/servlet/architecture.html
  • https://docs.spring.io/spring-security/reference/5.7/servlet/configuration/java.html
  • https://docs.spring.io/spring-framework/reference/web/webmvc/filters.html
저작자표시 비영리 변경금지 (새창열림)

'Framework > Spring' 카테고리의 다른 글

[Spring] Spring Security 인증 처리  (0) 2026.05.31
[Spring] @Transactional의 8가지 함정  (0) 2026.05.24
[Spring] @Transactional  (1) 2026.05.23
'Framework/Spring' 카테고리의 다른 글
  • [Spring] Spring Security 인증 처리
  • [Spring] @Transactional의 8가지 함정
  • [Spring] @Transactional
으노로
으노로
  • 으노로
    study-library
    으노로
  • 전체
    오늘
    어제
    • 분류 전체보기 (42) N
      • Language (16)
        • JAVA (15)
        • JavaScript (1)
      • Framework (4) N
        • Spring (4) N
      • Web (4)
      • Infra (6)
      • Algorithm (10)
        • Programmers (10)
      • Database (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    비동기 통신 방식
    트렌잭션
    자바
    programmers
    OS
    eclipse
    프로그래머스
    분수의덧셈
    @transactional
    문자열정렬하기(2)
    코딩테스트
    inmemorydb
    spring
    spring boot
    java
    문자열 정렬하기
    transactional
    알고리즘
    스프링부트
    스프링
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.6
으노로
[Spring] Spring Security
상단으로

티스토리툴바