1. 클라이언트 요청은 Controller로 바로 가지 않고, Servlet Container와 FilterChain을 먼저 지나간다.
25 min read
1강. 클라이언트 요청은 Controller 전에 FilterChain을 지난다
한 줄 결론
Spring Security는 Controller 내부 기능이 아니라 Servlet FilterChain 위에서 동작하므로, 인증/인가 문제는 Controller에 도착하기 전 단계에서 결정될 수 있다.
3줄 요약
HTTP 요청의 첫 도착지는 Controller가 아니라 Servlet Container다.
Servlet Container는 요청에 맞는 FilterChain을 실행하고, Spring Security는 이 구간에서 인증/인가, CORS, CSRF, 예외 처리를 수행한다.
Controller breakpoint가 걸리지 않는 401/403/302/CORS 문제는 먼저 Security FilterChain 통과 여부부터 확인해야 한다.
체크포인트
Controller가 요청의 첫 번째 도착지가 아닌 이유를 설명할 수 있는가?
Filter와 FilterChain이 요청을 다음 단계로 넘기는 방식을 설명할 수 있는가?
chain.doFilter(request, response)를 호출하지 않으면 어떤 일이 생기는지 말할 수 있는가?
401, 403, 302, CORS 오류를 Controller보다 앞단에서 의심해야 하는 이유를 설명할 수 있는가?
Security 적용 전후 요청 흐름을 비교할 수 있는가?
전체 흐름
흔히 생각하는 MVC 요청 흐름의 한계를 확인한다.
실제 요청이 Servlet Container와 FilterChain을 먼저 지나는 구조를 본다.
Filter와 chain.doFilter()가 요청 흐름을 제어하는 방식을 이해한다.
Spring Security가 FilterChain 어디에 끼어드는지 확인한다.
401/403뿐 아니라 302, CORS, CSRF 403, 404, 405를 함께 해석하는 기준을 잡는다.
Controller breakpoint가 걸리지 않을 때 요청이 어디서 멈췄는지 추적하는 사고법을 정리한다.
분리 후보
현재 문서는 Controller 전단의 요청 흐름을 한 번에 잡는 목적이므로 유지한다. 다만 길이가 부담되면 Servlet Container와 FilterChain 기본 흐름, Spring Security FilterChain과 인증/인가, 401/403/302/CORS 문제 추적법의 3개 문서로 나눌 수 있다.
확인 질문
아래 4개에 답할 수 있으면 넘어가면 된다.
1. Controller는 요청의 첫 번째 도착지인가?2. Filter에서 chain.doFilter(request, response)를 호출하지 않으면 어떤 일이 발생하는가?3. Controller에 breakpoint를 걸었는데 401 또는 403이 나면서 멈추지 않았다. 이때 가장 먼저 의심해야 하는 구간은 어디인가?4. 보호 API가 예상대로 동작하지 않을 때 왜 401/403뿐 아니라 302, CORS, CSRF 403, 404, 405도 같이 봐야 하는가?
이 문서의 결론
이번 문서의 결론은 아래와 같다.
클라이언트 요청은 Controller로 바로 가지 않는다.요청은 먼저 Servlet Container에 도착하고,Servlet Container는 해당 요청에 적용될 FilterChain을 실행한다.Spring Security는 이 FilterChain 구간에 끼어들어,Controller에 도착하기 전에 인증/인가를 처리할 수 있다. 뿐만아니라, 예외 처리, CSRF, CORS 관련 처리를 수행할 수 있다.따라서 401/403 등의 문제를 볼 때는 ( 대표적으로 401, 403이 가장 자주 보이지만, 상황에 따라 302, 404, 405, CORS 오류, CSRF 403, 500도 FilterChain 또는 Security 설정과 연결될 수 있다.)보호 API가 예상대로 동작하지 않는 경우로, Controller 코드부터 보는 것이 아니라,요청이 Security FilterChain을 통과했는지,어느 필터에서 멈췄는지,어떤 보안 규칙에 의해 응답이 결정됐는지부터 봐야 한다.
핵심은 Spring Security는 Controller 내부 기능이 아니라 Servlet Filter 위에서 동작한다는 점이다.
1. 클라이언트가 HTTP 요청을 보낸다.2. 서버의 Servlet Container가 요청을 받는다.3. Servlet Container가 HttpServletRequest, HttpServletResponse를 만든다.4. 요청에 적용될 Filter들을 순서대로 실행한다.5. Spring Security도 이 Filter 구간에서 실행된다.6. 보안 처리를 통과하면 DispatcherServlet으로 요청이 넘어간다.7. DispatcherServlet이 어떤 Controller 메서드를 실행할지 찾는다.8. Controller가 실행된다.9. 응답은 다시 Filter들을 거꾸로 지나 클라이언트로 돌아간다.
한 마디로
Controller는 요청 처리의 핵심이지만, 요청의 시작점은 아니다.
Spring Security는 Controller보다 앞에서 요청을 검사할 수 있다.
Servlet Container란 무엇인가?
우리가 작성한 Controller는 그냥 Java 객체다.
브라우저가 보낸 HTTP 요청이 직접 Java 메서드를 호출할 수는 없다.
중간에서 HTTP 요청을 받고, Java 웹 애플리케이션이 처리할 수 있는 형태로 바꿔주는 실행 환경이 필요하다.
즉, 요청이 FilterChain에서 막히면 DispatcherServlet까지 가지 못한다.
그러면 당연히 Controller도 실행되지 않는다.
Filter는 왜 필요한가?
Controller마다 공통으로 해야 하는 일이 있다.
로그 남기기인코딩 처리CORS 처리인증 확인권한 확인CSRF 확인요청 추적 ID 부여압축예외 처리
이런 코드를 모든 Controller에 직접 넣으면 코드가 지저분해진다.
좋지 않은 예시
@GetMapping("/api/me")public UserResponse me(HttpServletRequest request) { // 1. Authorization header 확인 // 2. 토큰 추출 // 3. 토큰 만료 확인 // 4. 사용자 권한 확인 // 5. CORS 확인 // 6. 실제 비즈니스 로직 return userService.getCurrentUser();}
이러면 모든 Controller가 보안 코드로 오염된다.
그래서 Controller 앞에 공통 처리 계층을 둔다.
그게 Filter다.
Filter= Controller에 도착하기 전 요청을 검사하거나 가공할 수 있는 객체
Filter는 요청을 다음 단계로 넘길 수도 있고, 중간에 응답을 직접 만들어서 요청을 끝낼 수도 있다.
GET /api/admin/report→ Servlet Container→ FilterChain→ Spring Security FilterChain→ JWT 검증 성공→ Authentication 생성 성공→ 현재 권한: ROLE_USER→ 필요한 권한: ROLE_ADMIN→ AuthorizationFilter에서 거부→ 403 Forbidden→ Controller 도착 안 함
이 경우는 인증이 안 된 게 아니고, 권한이 부족하다.
그래서 403이 된다.
예시 3. API인데 302가 나오는 경우
REST API를 만들고 있는데, 요청 결과가 401이 아니라 로그인 페이지로 redirect 되는 경우가 있다.
GET /api/me
응답이 이런 식으로 올 수 있다.
302 FoundLocation: /login
이 경우 흔한 원인은 이런 설정이다.
formLogin이 켜져 있음인증 안 된 사용자를 로그인 페이지로 보내는 기본 흐름이 동작함
API 서버에서는 보통 JSON 응답이나 401을 기대한다.
그런데 form login 흐름이 살아 있으면 브라우저 기준 로그인 페이지로 redirect될 수 있다.
즉, 이것도 Controller 문제가 아닐 수 있다.
Security의 인증 실패 처리 방식 문제일 수 있다.
예시 4. CORS 오류
프론트엔드에서 API를 호출했는데 브라우저 콘솔에 CORS 오류가 뜬다고 하자.
Access to fetch at 'http://localhost:8080/api/me'from origin 'http://localhost:3000'has been blocked by CORS policy
이때 서버 Controller에 breakpoint를 걸어도 안 멈출 수 있다.
왜냐하면 브라우저가 실제 요청 전에 preflight 요청을 먼저 보낼 수 있기 때문이다.
OPTIONS /api/meOrigin: http://localhost:3000Access-Control-Request-Method: GET
이 OPTIONS 요청이 Security/CORS 설정에서 막히면 실제 GET /api/me는 보내지지 않을 수도 있다.
흐름은 이런 식이다.
Browser→ OPTIONS /api/me preflight→ Security/CORS 설정에서 거부→ 브라우저가 실제 GET 요청 차단→ Controller 도착 안 함
따라서 CORS 오류도 단순 Controller 문제가 아니다.
예시 5. CSRF 때문에 403이 나는 경우
Spring Security에서 CSRF 보호가 켜져 있는데, 아래 요청을 보낸다고 하자.
POST /api/auth/logoutCookie: JSESSIONID=...
CSRF token이 없다면 403이 날 수 있다.
POST /api/auth/logout→ Servlet Container→ FilterChain→ Spring Security FilterChain→ CsrfFilter→ CSRF token 없음→ 403 Forbidden→ Controller 도착 안 함
Client → Servlet Container → FilterChain → Spring Security FilterChain → DispatcherServlet → Controller → Service
자주 하는 오해
오해
교정
요청은 Controller로 바로 간다
실제로는 Servlet Container와 FilterChain을 먼저 지난다
401/403만 Security 문제다
302, CORS, CSRF 403, 500도 Security 설정과 연결될 수 있다
403은 항상 권한 부족이다
CSRF token 누락 때문에 403일 수도 있다
Controller breakpoint가 안 걸리면 요청이 안 온 것이다
요청은 왔지만 FilterChain에서 끝났을 수 있다
JWT면 SecurityContextHolder를 안 쓴다
JWT도 현재 요청 중에는 Authentication을 SecurityContextHolder에 저장한다
CORS 오류는 Controller에서 해결한다
많은 경우 preflight가 Controller 전에 처리된다
404는 항상 URL 오타다
requestMatcher와 Controller mapping 불일치도 함께 봐야 한다
302는 정상 응답이다
API 서버에서는 formLogin redirect가 의도치 않은 설정일 수 있다
핵심 정리
웹 요청은 Controller로 바로 가지 않는다.요청은 Servlet Container에 먼저 도착하고,FilterChain을 통과한 뒤,DispatcherServlet을 거쳐 Controller로 간다.Spring Security는 이 FilterChain 구간에서 동작한다.따라서 보호 API가 예상대로 동작하지 않을 때는Controller 코드부터 보는 것이 아니라,요청이 Security FilterChain에서 어떻게 처리됐는지부터 봐야 한다.대표적인 증상은 401과 403이지만,실전에서는 302 redirect, CORS 오류, CSRF 403, 404, 405, 500도 함께 봐야 한다.중요한 것은 HTTP 상태 코드 하나를 외우는 것이 아니라,요청이 Controller까지 도착했는지,FilterChain에서 끝났는지,DispatcherServlet 이후에서 실패했는지를 구분하는 것이다.