코드노트

[Spring Security] 클라이언트 요청이 보안 필터에 도달하는 과정 정리 본문

Code note/springboot

[Spring Security] 클라이언트 요청이 보안 필터에 도달하는 과정 정리

코드노트 2026. 7. 28. 17:15

 

Spring Security로 관리자 페이지를 제어하면서 클라이언트 요청부터 시작해서 서버까지 오는 과정 중 보안필터에 도달하는 과정을 정리해보려고 한다.

 

처음 제어 구현을 시작할때 프론트적인 사고를 하면서 인터셉터를 생각했다

관리자만 접근을 해야하고 API 접근에도 그렇고 세션을 확인해야한다면 각 요청마다 진행되어야하는데 그럼 인터셉터를 해야하지않을까? 했지만 백엔드에서도 당역히 단순한 작업은 HandlerInterceptor라는 인터셉터로 구현할 수 는 있다.

하지만 보안/인증 영역으로 넘어가게되면 인터셉터 만으로는 한계가 생길 수 있다.

 

그래서 적용하면서 이 부분은 한번 제대로 정리하고 넘어가는게 좋을것 같다고 판단했다.

 

 


 

 

먼저 서블릿과 필터에 대해서 용어를 정리해보자



 

서블릿(Servlet)이란

- 웹 요청(HTTP)을 받아 응답을 만들어내는 자바의 최종 담당자이다.

  • 클라이언트가 GET /admin/users 같은 HTTP 요청을 보내면 자바 코드 입장에서는 이를 해석해서 DB를 조회하고 JSON이나 HTML로 응답을 만들어줘야 한다.
    • 이 작업을 수행하는 자바의 웹 표준 인터페이스/객체가 서블릿
    • 동작 방식 : 요청 하나가 들어오면 HttpServletRequest(요청 정보)를 읽고, HttpServletResponse(응답할 내용)에 데이터를 담아 보낸다.
    • 스프링과의 관계 : 스프링 MVC도 결국 서블릿 기반임 - 스프링은 DispatcherServlet이라는 단 하나의 거대한 대표 서블릿을 두고 있는데 여기서 모든 요청을 이 서블릿이 받아 적절한 컨트롤러(@RestController)로 배분하는 방식으로 진행됨
서블릿은 웹 요청을 받아서 자바 코드로 즉 컨트롤러로 연결을 해주고 나온 응답결과를 받아서 다시 내보내는 역할이다.
이것을 서블릿이라고 부른다. 이 개념은 까먹지 말자.

필터(Filter)란

- 서블릿에 요청이 도달하기 전과 후에 거치는 보안 검문소라고 보면 된다.

  • 클라이언트의 요청이 서블릿에게 가기 전 입구에서 검문한다
    • 너 토큰 있음?
    • 너 해킹 공격 아님?
      • CSRF : 로그인 사용자를 속여 원치 않는 요청을 몰래 보내는 공격
      • XSS : 사용자의 브라우저에 악성 스크립트를 심어 정보를 훔치는 공격
  • 서블릿 컨테이너(Tomcat) 레벨 : 필터는 스프링 엔진 내부가 아닐, 웹 서버(Tomcat)와 스프링 엔진 사이의 최외각 경계선에 존재함
  • Filter Chain : 필터는 여러 개를 사슬처럼 연결 (Filter1 > Firter2 > Filter3 ... ) 할 수 있음
  • 스프링 시큐리티의 비밀?
    • 스프링 시큐리티 역시 내부적으로 수 많은 서블릿 필터들(FilterChainProxy)이 사슬처럼 엮여서 인증/인가를 처리하는 구조임
필터는 서블릿으로 가기전 검문소이다. 여기서 체인 형식으로 여러개를 사슬처럼 순차적으로 엮을 수 있다. 즉 자바로 접근하기전 여러 검사를 통해 접근을 막거나 통과시킬 수 있다.

자 그럼 생각 정리를 해보자면 스프링 컨트롤러나 인터셉터까지 요청이 들어가려면 스프링 프레임워크의 복잡한 객체 생성 및 로직이 작동을 해야한다. 그전 유효하지 않은 토큰, 해킹 등등 굳이 스프링 내부까지 들여보낼 필요 없이 최전방인 필터 단에서 
바로 차단이 가능하다.
서버 자원 보호와 보안상 훨씬 유리하다.

 

 

전체 흐름 순서

프론트

  • 사용자 이벤트 API (fetch, axios)
    • 프론트 인터셉터 (로컬 스토리지/ 쿠키에서 토큰을 꺼내 Authorization 헤더 추가
    • 기본 설정 : Base URL, Content-Type 등 공통 헤더 설정
      • 네트워크 송신
      • 브라우저가 HTTP 프로토콜 형태로 백엔드 서버 URL로 데이터 패킷 송신

백엔드

  • 서블릿 컨테이너(Tomcat)
  • 가장 앞에 있는 출입문, 네트워크로 넘어온 HTTP 요청 패킷을 받아 자바 객체로 변환
    • 필터(Filter / Spring Security)
    • HTTP 헤더의 JWT 토큰 유효성 검증, CORS 허용 여부 체크, IP 차단 등
    • 검증 실패 시 컨트롤러까지 전달하지 않고 여기서 401, 403 응답 후 종료
      • 디스패치 서블릿 (DispatcherServlet)
      • 필터를 무사히 통과한 안전한 요청을 받아 요청 URL을 보고 어떤 컨트롤러로 보내야할지 판단
        • 스프링 인터셉터(Sprint Interceptor)
        • 스프링 내부 공통 로직 처리함
        • 컨트롤러 직전에 세부 권한을 확인 및 요청 로그 기록, 처리 시간 측정 등 실행됨
          • 컨트롤러 (Controller) & 서비스 (Service)
          • 실제 비즈니스 로직 수행 후 DB조회(@RestController)
          • 결과물 JSON 등의 데이터 형식으로 응답(HttpServletResponse) 생성
  • 그 이후로는 역순으로 응답 전달됨
    • Controller > Interceptor > DispatcherServlet > Filter > Tomcat >>>> HTTP 응답 송신

 

 

 

서블릿 컨테이너(톰캣)와 스프링 컨테이너 차이

- 스프링 시큐리티의 진입 매커니즘을 이해하기 위해 가장 먼저 알아야하는 핵심은 웹 서버 안에는 서로 완전히 격리된 두개의 컨테이너가 존재 한다.

 

서블릿 컨테이너(Tomcat)

- 클라이언트(브라우저)가 웹 서버에 HTTTP 요청을 보내게 되면 가장 먼저 요청을 받는 외부 출입구이다.

- 주요 역할 : 자바 웹 표준에 맞춰서 작성된 Filter와, Servlet을 관리

- 웹 요청이 들어오게되면 서블릿 컨테이너는 자신이 등록받은 서블릿 필터들을 순서대로 통과시킨다.

- 톰캣은 스프링 프레임워크와 상관없이 독자적으로 떠 있는 컨테이너다.

스프링 안에서 어떤 일이 일어나는지 어떤 빈(Bean)이 존재하는지 전혀 모른다.

 

스프링 컨테이너

- 우리가 작성한 컨트롤러, 서비스, 리포지토리, 보안 설정 등 핵심 애플리케이션 로직 있는 곳

- 주요 역할 : @Component나 @Bean으로 등록된 객체들을 생성하고, 의존성 주입(DI)을 해주며 라이프 사이클을 관리

- Spring Security의 모든 핵심 로직(비밀번호 검증, DB 사용자 조회, 토큰 검증 등등)은 스프링 빈으로 등록되어

다른 스프링 객체들과 협력해야만 제대로 동작할 수 있다.

 

 

여기서 발생하는 구조적인 문제가 있음

- 보안 검사를 하려면 요청이 들어오는 길목인 서블릿 컨테이너에 필터를 통해야함

근데? 정작 실제 보안 검사를 수행하는 핵심 로직은 스프링 컨테이너 안에 스프링 빈형태도 갇혀 있음

톰캣은 스프링 빈을 다를 줄 모르기 때문에 톰캣의 필터 목록에 스프링 빈으로 만들어진 보안 객체를 직접 꽂아 넣는 것이 불가능함

이처럼 요청을 가로채는 위치인 서블릿 컨테이너(톰캣)와 실제 코드를 가진 스프링이 서로 다른 세계에 찢어져 존재하고 있다는 구조적 차이를 이해해야함

 






 

두 컨테이너에 다리역할을 하는 껍데기 DelegatingFilterProxy

- 위에서 말했듯이 톰캣은 스프링 빈을 직접 호출할 수 없다.

이 두세계의 단절을 해결하기 위해 스프링이 제시한 해결책이 바로 DelegatingFilterProxy라는 Bridge 클래스다.

 

DelegatingFilterProxy 이름을 보게 되면 역할이 그대로 나온다.

Filter : 톰캣 입장에서 보면 그냥 평범한 서블릿 필터 중 하나이다. 그래서 톰캣의 필터 체인에 문제없이 정상 등록 된다.

Proxy (대리인) & Delegating (위임) : 겉모습은 필터이지만 자기 내부에 실제 보안 로직을 수행하는 코드가 단한줄도 없다.

실제 업무가 다른 사람에게 100% 떠넘기는 껍데기일 뿐이다.

 

 

이제 요청을 받으면 일어나는 단계이다.

  1. 스프링 컨테이너 접속 : DelegatingFilterProxy에 도달하면 다음과 같은 순서로 일이 진행된다.
  2. 특정 빈(Bean) 검색 : 스프링 컨테이너 안에서 springSecurityFilterChain이라는 이름으로 등록된 자바 빈을 찾음
  3. 일거리 전달(위임) : 자기가 받은 HttpServletRequest와 HttpServletResponse 객체를 그 빈에게 넘겨주면서 실제 보안 검사는 네가 알아서 해 하고 제어권을 완전히 맡겨버림

왜 이렇게 해야함?

- 웹 요청을 받는 창구는 톰캣에 둘 수밖에 없지만 보안을 처리하는 모든 주도권과 자바 객체 관리를 스프링 생태계 안으로 가져오기위해서 껍데기를 놓는 것이다.






스프링 내부에서 관리하고 실행하는 FilterChainProxy

FilterChainProxy

- DelegatingFilterProxy가 이제 서블릿 컨테이너(톰캣)에서 가져온 요청을 스프링 컨테이너 내부에서 받아내는 스프링 빈이다.

 

그래서 무슨 역할임?

- 단일 진입점 : 외부에서 들어오는 모든 보안 관련 요청을 스프링 내부에서 가장 먼저 받아내는 단 하나의 대표 빈

- SecurityFilterChain의 관리자 : 내부에 여러 개의 보안 필터 체인 목록을 가지고 있음

- 라우팅(분기 처리) : 들어온 요청의 URL을 확인한 뒤 요청에 따라 A필터 체인, B필터 체인에 맡길지 판단 후 적절한 필터 목록으로 연결함

 

 

FilterChianProxy <-> SecurityFilterChain 관계

- FilterChianProxy(단 1개만 존재함) : 보안 요청 입구를 지키며 전체 판을 짜고 관리함

- SecurityFilterChain (1개 이상) : 실제 보안 필터들의 모임 들임

      ex ) /api/** 요청용 체인에는 JwtAuthenticationFilter등을 넣고 /login 요청용 체인에는 UsernamePasswordAuthenticationFilter를 묶거나 각각 다른 필터를 구성 가능함

 

지금 까지 내용을 정리하자면
DelegatingFilterProxy가 스프링 컨테이너에서 springSecurityFilterChain이라는 빈을 찾아 간다고 했는데 그게 바로 FilterChainProxy이다.
- FilterChainProxy내부에서 실행되는 필터들은 일반 서블릿 필터가 아니라 스프링 시큐리티가 제공하는 보안 전용 필터들이다.
ex) 인증 필터, 인가 필터, LogoutFilter 등
이 덕분에 스프링의 모든 기능을 필터 안에서 누릴 수 있다.
- 이 외에도 보안 필터 체인을 통과하다가 실패401, 권한403 등의 에러들도 이를 가로채서 예외 응답을 만들어 보내는 시큐리티 내부 예회 처리 흐름도 이 FilterChainProxy의 제어 아래에서 작동한다.

 

 






최종 정리

 

1. 외부 진입 (Servlet Container)

- 클라이언트의 HTTP 요청이 웹 서버에 도착하면 서블릿 컨테이너가 갖고 있는 일반 필터 체인이 실행됨

 

2. 다리 건너기 (DelegatingFilterProxy)

- 톰캣에 등록된 껍데기 필터인 DelegatingFilterProxy가 요청을 가로채고 스프링 컨테이너에서 springSecurityFilterChain이라는 이름의 빈을 검색함

 

3. 지휘간 접수 (FilterChainProxy)

- 검색된 FilterChainProxy 객체가 요청을 이어 받으면서 제어권이 완벽하게 스프링 프레임워크 내부로 전환됨

 

4. 보안 검사 컨트롤러까지 도달

- FilterChainProxy가 요청 URL에 맞는 SecurityFilterChain을 선택하고 내부의 수많은 보안 필터들(인증, 인가, 세션 등)을 순서대로 통과 시킴

- 이상이 없으면 비로소 스프링의 DispatcherServlet을 거쳐 우리가 작성한 Controller에 요청이 도착하게됨

 

 


여기서 이제 HTTP Basic, Form Login, JWT토큰 방식중 뭘 써도 서비스에서 FilterChainProxy까지 들어오는 진입 메커니즘은 동일함