[TIL 66-3일 차] Spring Security 기초
7. 커스텀 Filter 구현
7-01. 커스텀 Filter의 필요성
1) 기본 Filter의 한계
Spring Security는 다양한 보안 기능을 제공하기 위해 여러가지의 Filter를 기본적으로 제공한다.
다만, 특정 비즈니스 로직이나 추가적인 보안 정책을 적용하려고 할 때, 내장 Filter만으로는 한계가 있다.
아래의 경우에 커스텀 Filter가 필요
- 모든 요청에 대해 추가적인 로깅 수행
- 특정 헤더 값이 반드시 포함되어야 하는 규칙 적용
- 사용자 IP 주소 검증 or 화이트리스트 정책 적용
2) 커스텀 Filter
기본 Filter Chain 앞/뒤의 원하는 위치에 삽입 가능
- 꼭 필요한 경우에만 사용할 것
- 보안과 관련된 중요한 정책은 표준 Filter로 먼저 처리하고, 커스텀 Filter는 부가적인 기능에 사용
커스텀 Filter 필요성
- 추가 인증 단계
- 기본 인증 후, 별도의 토큰 검증이나 OTP 인증 추가
- 로깅 및 감사(Audit)
- 요청과 응답에 로그를 남겨 추적 가능성 ⬆️
- 보안 정책 적용
- 특정 국가 IP 차단, User-Agent 기반 접근 제한, 특정 시간대 요청 차단 등
- 성능 확인 및 모니터링
- 요청 처리 시간 기록하여 성능 문제 추적
7-02. 커스텀 Filter 구현 방법
1) GenericFilterBean 확장
Spring Security에서 Filter를 구현할 때 가장 기본적으로 사용할 수 있는 클래스
jakarta.servlet.Filter를 구현한 추상(abstract) 클래스- Spring Bean으로 등록 가능하게 지원
- Filter 구조를 직접 제어해야 할 때 유용
// GenericFilterBean을 상속하여 Filter 구현
public class CustomGenericFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// 1. 요청 전 처리 로직
System.out.println("[CustomGenericFilter] 요청 처리 전 실행");
// 2. Filter Chain 계속 진행
chain.doFilter(request, response);
// 3. 응답 후 처리 로직
System.out.println("[CustomGenericFilter] 응답 처리 후 실행");
}
}
실무 활용
- 사용자 세션 만료 처리
- IP 화이트리스트
- ➡️ 요청과 응답을 모두 다루며, 전후 처리 제어가 필요한 경우 주로 사용
2) OncePerRequestFilter 활용
Spring Security에서 커스텀 Filter를 구현할 때 가장 많이 사용하는 클래스
- 중복 실행 방지 : 한 요청당 한 번만 실행되도록 보장
- 인증/인가 검증, 로깅, 헤더 검증 등의 작업에 활용
public class CustomOncePerRequestFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// 1. 요청 전 처리
String clientIp = request.getRemoteAddr();
System.out.println("[CustomOncePerRequestFilter] 클라이언트 IP: " + clientIp);
// 2. 필터 체인 진행
filterChain.doFilter(request, response);
// 3. 응답 후 처리
System.out.println("[CustomOncePerRequestFilter] 응답 완료");
}
}
실무 활용
- JWT 토큰 검증
- 요청 추적 ID 로깅
- ➡️ 요청마다 반드시 한 번 실행되어야 하는 보안/로그 추적 로직에 적합
7-03. Filter Chain에 커스텀 Filter 추가
1) 특정 위치에 Filter 추가
Spring Security에서는 Filter Chain에 커스텀 Filter를 원하는 위치에 추가 가능
보통 SecurityFilterChain 구현할 때 addFilterBefore, addFilterAfter, addFilterAt 메서드 사용
- 아래의 코드는
UsernamePasswordAuthenticationFilter실행 전 커스텀 Filter를 추가
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(new CustomOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class)
//...
}
}
addFilterBefore: 지정된 Filter 앞에 커스텀 Filter 추가addFilterAfter: 지정된 Filter 뒤에 커스텀 Filter 추가addFilterAt: 지정된 Filter와 같은 위치에 추가
2) URL 패턴 기반 Filter 적용
특정 URL 패턴에 커스텀 Filter 적용 가능
방법1: 단일 Chain + 내부 분기
- 하나의
SecurityFilterChain만 존재 - 모든 요청에 같은 Filter Chain을 거침
- Filter 내부에서
request.getRequestURI()를 검사해 조건 분기 - 장점: 구현 단순
- 단점: 모든 요청이 Filter를 거침 ➡️ 불필요한 요청에도 실행 가능
방법2: 다중 SecurityFilterChain 구성
- URL 패턴별로 별도의
SecurityFilterChain을 정의 - 장점: 불필요한 요청은 제외되므로 성능과 안정성이 올라감
- 단점: Chain이 여러 개 생기므로 관리가 복잡해
8. 주요 웹 보안 이슈와 Spring Security 방어 전략
8-01. CSRF(Cross-Site Request Forgery) 이해와 방어
CSRF는 사용자가 의도하지 않은 요청을 공격자가 위조하여 서버에 보내도록 하는 공격 기법
- 피해자는 이미 로그인된 세션을 가진 상태이므로, 서버는 공격자가 보낸 요청을 정상 요청으로 오인
- ➡️ 사용자가 모르는 사이 계정 정보 변경, 송금, 글 작성 등의 행위 발생 가능

1) Spring Security의 CSRF 보호 메커니즘
기본 동작
Spring Security는 기본적으로 상태 유지 세션 기반 애플리케이션에서 CSRF 보호를 활성화
서버는 요청 시 CSRF 토큰을 검증하여, 클라이언트가 의도적으로 요청을 보냈는지 확인
- Spring Security 6.x에서는 CSRF가 기본적으로 활성화되어 있음
- JWT를
Authorization헤더에 담아 사용하는 Stateless Rest API 서버라면 인증 정보가 브라우저에 의해 자동 전송되지 않으므로 일반적으로 CSRF 비활성화 - 세션 기반 웹 앱은 CSRF 보호 필수
SameSite,Secure,HttpOnly쿠키 옵션 적극 활용
CsrfFilter와 CsrfToken
- CsrfFilter
- 모든 요청에 대해 CSRF 토큰을 검증하는 Filter
- 사용자가 보낸 요청에
_csrf파라미터나 헤더 값이 없는 경우AccessDeniedException을 발생
- CsrfToken
- 서버가 세션별로 생성하여 클라이언트에게 전달하는 토큰
- 매 요청마다 클라이언트가 전달하며, 서버는 저장된 토큰과 일치 여부 검사
- 토큰 생성 과정
- 사용자가 최초로 세션을 생성하거나 페이지를 요청할 때 고유한 난수 기반 토큰을 생성
- 난수는
SecureRandom기반으로 생성되어 예측이 불가능하고, 위조가 어려움
- 난수는
HttpSessionCsrfTokenRepository(기본 구현체) 또는CookieCsrfTokenRepository에 저장됨
- 사용자가 최초로 세션을 생성하거나 페이지를 요청할 때 고유한 난수 기반 토큰을 생성
- 토큰 전달 방식
- Form hidden 필드: 서버에서 렌더링 시
_csrf값을 자동 삽입 - AJAX/SPA: 응답 헤더 또는 meta 태크로 전달 후, 요청 시
X-CSRF-TOKEN헤더에 포함 - Double-submit 쿠키: 토큰을 쿠키와 헤더 모두에 담아 서버에서 일치 여부 검증
- Form hidden 필드: 서버에서 렌더링 시
- 검증 방식
- 요청이 들어오면
CsrfFilter동작 - 요청에 포함된 토큰(
_csrf필드 또는 헤더 값)을 추출 - 저장소(
HttpSession또는Cookie)에 보관된 토큰과 비교 - 일치하면 정상 요청, 불일치 또는 누락 시
AccessDeniedException발생- 공격자는 올바른 토큰을 알 수 없으므로 위조 요청은 차단됨
- 요청이 들어오면
- CsrfFilter 동작 순서
- loadToken() ➡️ 저장소에서 토큰 조회
- 없으면 generateToken() ➡️ saveToken()
- 요청 메서드가 POST/PUT/DELETE/PATCH면 토큰 검증
- 토큰 불일치 ➡️ AccessDeniedException
- 보강 전략
- Origin/Referer 검증: 요청 헤더로 동일 출처인지 확인
- SameSite 쿠키:
Lax또는Strict설정으로 크로스 도메인 요청 차단 - XSS 방어: 토큰 탈취 방지를 위해 필수
8-02. XSS(Corss-Site Scripting) 이해와 방어
XSS는 공격자가 악의적인 스크립트를 웹 페이지에 삽입하여 사용자의 브라우저에서 실행되도록 하는 공격 기법
- 쿠키 탈취, 세션 하이재킹, 악성 사이트 리다이렉션, 피싱 등에 활용됨

1) XSS 주요 유형
- Stored XSS
- Reflected XSS
- DOM 기반 XSS
2) Spring Security의 XSS 방어 접근법
기본 응답 헤더 설정: CSP와 nosniff
브라우저가 응답 헤더를 근거로 위험한 Resource/스크립트를 차단하도록 하는 접근법
- CSP(Content-Security-Policy)와 X-Content-Type-Options: nosniff는 현제 XSS 방어에서 가장 많이 사용하는 방식
- CSP(Content-Security-Policy): Resource/스크립트 로딩 및 실행 원천 제한
- X-Content-Type-Options: MIME 스니핑 방지하여 잘못된 타입의 Resource 실행 및 랜더링 금지
3) CSP (Content-Security-Policy)
브라우저가 강제하는 보안 정책으로, 어떤 출처(origin)의 Resource만 로드/실행 가능한지를 선언적 헤더로 규정
- origin = protocol + host(domain) + port
- URL의 프로토콜, 호스트, 포트를 합친 값
- 예시 : https://example.com:443
- XSS 방어의 최전선: 스크립트 실행 원천을 제한하고, 인라인 스크립트 금지 또는 허용된 nonce/hash만 실행하게 함
전략
nonce: 요청마다 서버가 난수를 생성하여<script nonce="<값>">에서만 실행 허용- Thymeleaf 등의 서버사이드 렌더링에 사용
hash: 스크립트 내용을 SHA-256 등의 해시값으로 계산한 뒤, CSP 정책에 등록된 해시값과 일치하는 스크립트만 실행 허용
8-03. CORS(Cross-Origin Resource Sharing) 이해와 설정
CORS는 교차 출처 Resource 공유를 의미
서로 다른 Origin을 가진 애플리케이션이 서로의 Resource에 접근할 수 있도록 해줌
1) 동일 출처 정책(SOP)
브라우저는 SOP를 적용하여 다른 출처의 Resource 접근을 차단
- 장점: 외부 Resource를 무분별하게 가져오는 것을 막아, XSS·XSRF 공격 방어 가능
- 한계: SOP로 인해 기본적으로 제한되는 다른 출처 요청을 서버가 허용한 경우에만 브라우저가 접근할 수 있도록 하는 예외 메커니즘 CORS가 있음
2) CORS 동작 원리
Simple Request(단순 요청)와 Preflight Request(예비 요청) 두 가지 존재
Simple Request(단순 요청)
서버에 바로 요청을 보내고, 서버는 Access-Controle-Allow-Origin 헤더를 포함한 응답을 반환
브라우저는 이 헤더를 확인해 요청 허용 여부 결정

- Simple Request 조건
- 요청 메서드는
GET,HEAD,POST중 하나 Accept,Accept-Language,Content-Language,Content-Type등 제한된 헤더만 사용 가능Content-Type은application/x-www-form-urlencoded,multipart/form-data,text/plain만 허용
- 요청 메서드는
Preflight Request(예비 요청)
본 요청 전에 OPTIONS 메서드로 예비 요청을 보
서버가 허용 여부(Access-Control-Allow-* 헤더)를 응답하면, 브라우저가 본 요청을 진행합니다.
- 대부분의 REST API는
application/json을 사용하기 때문에 Preflight 요청으로 처리됨

Leave a comment