Spring Security는 쉽지 않은 녀석이였따
Spring Security는 처음 접했었을떄
“인증은 어디서 하고, 인가는 왜 또 따로 있을까”
“필터는 왜 이렇게 많을까..?”
나도 최근 JWT 기반 인증 흐름을 리팩토링하면서
Security가 어려운 이유가 내가 구조 이해못해서라는 것을 느꼈다.
내가 초보 개발자라..
1. Spring Security가 어려운 이유
Spring Security는 “요청이 들어왔을 때 어떤 필터를 거쳐서 인증·인가를 수행할지”를 먼저 정의하는 프레임워크다.
많은 사람이 Spring Security를 Controller 쪽에서 작동한다고 생각하는데,
사실은 그 이전, DispatcherServlet보다 훨씬 앞단에서 모든 게 시작된다.

즉, 우리가 작성한 Controller 코드가 실행되기도 전에
Security Filter Chain이 먼저 요청을 훑는다.
그래서 Security를 잘 모르면 다음과 같은 상황이 생긴다:
- 내가 만든 Controller가 호출되지 않음
- JWT 필터가 동작 안 함
- SecurityConfig가 너무 거대해짐
- 어디서 인증이 거부되는지 디버깅이 어려움
Security 흐름을 먼저 이해해야만 요청과 응답의 흐름을 읽을 수 있따
2. DispatcherServlet 이전에 존재하는 Security Filter Chain
Spring MVC 전체 흐름을 단순화하면 대략 이런 구조다:
[요청] → (서블릿 필터들) → DispatcherServlet → Controller → Service → Repository
여기서 Spring Security는 ‘서블릿 필터들’ 단계에 개입한다.
즉, 요청이 들어오면 Security Filter Chain이 가장 먼저 요청을 가로챈다.
그럼 필터가 무엇일까?
필터 = 요청을 가로채서 선행 작업을 수행하는 미들웨어.
JWT 인증 검사, 로그인 체크, 권한 확인 같은 건 모두 여기서 이뤄진다.
Spring Security에서 자주 보는 필터들:
- UsernamePasswordAuthenticationFilter
- JwtAuthenticationFilter (우리가 커스텀하는 경우)
- ExceptionTranslationFilter
- FilterSecurityInterceptor
이 필터들은 순차적으로 실행되는데,
순서가 조금만 어긋나도 인증이 안 되거나,
예외 처리가 작동하지 않는 등 사고가 터진다.
3. UsernamePasswordAuthenticationFilter의 역할
보통 Security의 기본 로그인은 이 필터가 담당한다.
플로우는 단순하다:
- /login 요청이 들어오면
- 아이디/비밀번호를 꺼내서 Authentication 객체 생성
- AuthenticationManager에게 전달
- Provider가 실제로 인증
- 성공하면 SecurityContext에 인증 정보 저장
하지만 우리가 JWT 기반 로그인을 적용하면
이 필터는 거의 무용지물이 되고,
그 자리를 커스텀 필터로 대체해야 한다.
그래서 필터 구조를 잘 모르면
“왜 JWT 인증이 안 되지?” 같은 상황이 생긴다.
4. AuthenticationManager / AuthenticationProvider 관계
Security 인증 흐름을 이해할 때 밑 내용을 참조하면 조금 더 편한데
사용자 요청
↓
AuthenticationFilter
↓
AuthenticationManager
↓
AuthenticationProvider
↓
UserDetailsService (DB 조회)
↓
성공/실패 판단
정리하면…
- Filter: 요청을 받아 매니저에게 넘김
- Manager: 여러 Provider 중 어떤 걸 사용할지 선택
- Provider: 실제 인증 (비밀번호 검증 등)
- UserDetailsService: 유저 정보 조회
JWT 방식에서도 흐름은 비슷한데, 인증 정보가 DB가 아니라 토큰에서 유출된다는 점만 다르다.
그래서 Provider를 건드리기보다는 Filter를 커스텀해서 JWT 해석/인증을 대신해주는 방식을 쓴다.
5. JWT와 Security가 만나면 왜 더 헷갈릴까?
JWT 자체는 단순한 문자열일 뿐인데, Security와 합치면 갑자기 난이도가 상승한다…
왜 그럴까 흑흑
1. “JWT를 언제 검증해야 하는지” 타이밍을 잡기 어렵다
- Filter Chain 어디에 넣어야 하는지
- UsernamePasswordAuthenticationFilter 앞인지 뒤인지
- CORS 처리 전에 해야 하는지
이런 것들이 전부 난이도를 올린다.
사실 JWT의 경우에도 어떤 전략을 쓰는지에 따라 다 달라져서도 어느정도 영향을 미치는거 같다
정답을 알려줘
2. 인증 실패와 인가 실패가 다르다는 사실
- 인증 실패 → AuthenticationEntryPoint
- 인가 실패 → AccessDeniedHandler
이 둘이 다르다는 걸 모르고 같은 곳에 처리하려다 꼬이는 경우가 많다.
3. SecurityConfig 구조가 지나치게 방대해진다
jwt 필터, oauth 설정, csrf, cors, exception handler, path matcher…
이 모든 걸 한 파일에 적으려니 복잡해질 수밖에 없다.
6. 내가 이번에 공부하며 깨달았던 점.
1. 필터 순서를 이해해야 문제 해결이 된다
JWT 필터를
UsernamePasswordAuthenticationFilter 앞에 둘지, 뒤에 둘지에 따라 동작이 완전히 달라진다
JWT 인증은 “로그인 처리”가 아니라 “요청 검증”이기 때문에
보통 UsernamePasswordAuthenticationFilter 앞에 둔다.
2. 인증/인가 예외 처리를 분리해야 한다
처음에는 모든 에러를 하나의 핸들러로 처리했는데
보안 관점에서는 완전히 구분하는 게 맞다.
- 토큰이 없거나 만료됨 → 인증 에러
- 권한이 부족함 → 인가 에러
이 둘을 분리하니 코드도 깔끔해지고 디버깅도 쉬워졌다.
3. SecurityConfig는 “설정 집합”이지 “코드 집적 파일”이 아니다
초기에는 Config에 너무 많은 로직이 들어가 있었는데
결국 다음과 같이 분리하는 게 가장 바람직했다.
- SecurityConfig
- JwtFilterConfig
- ExceptionHandlingConfig
- OAuth2Config
- CorsConfig
Config를 쪼개니 변경 포인트도 줄고, 가독성도 올라갔다.
마무리
Spring Security가 어려운 이유는 결국 하나다.
인증 로직을 우리가 직접 구현하는 게 아니라, 필터 체인 속에 끼워 넣는 방식으로 만들기 때문이다.
그래서 Security는 단순히 코드만 보고 이해하려고 하면 절대 안 되고,
전체 흐름을 먼저 잡고 디테일을 채워나가야 한다.

허헣 이젠 분석할 수 있겠서,,
'Framework & Library > Spring' 카테고리의 다른 글
| [Spring] Spring Profile을 활용한 환경 구축 (1) | 2026.01.18 |
|---|---|
| [Spring] 리액티브에서의 Scheduler와 Context 그리고 Testing (0) | 2025.10.20 |
| [Spring] Backpressure와 Sinks 이해하기 (0) | 2025.10.20 |
| [Spring] 리액티브 시스템? 리액티브 프로그래밍? (0) | 2025.10.17 |
| [Spring] 리액티브 프로그래밍을 들어가며 (0) | 2025.10.12 |