
작년 10월 말부터 진행했던 고도화 프로젝트가 개발 서버에서 통합 QA를 진행하고 있다.
그리고 우리 프로젝트는 네이티브 개발자 분들이 WebView 사용과 API호출 등 연결을 해주고 있다.
그래서 개발서버의 QA중인 고도화 서비스는 네이티브 개발자 분들 , 그리고 관계사 관리자 분들이 요즘 사용해 주고 있어서
매일 개발서버 로그를 검수하며 문제는 없는 지 체크를 해보고 있다.
오늘도 로그를 보면서 Exception 걸린 부분이 있는지 검토하고 있었다.
그러다 PROPFIND 라는 Exception 문구가 있는 게 아닌가?
이건 또 뭐다냐😟 하면서 구글에 검색해 보았다.
내용을 알게 되고 서비스 운영 하는 개발자 분들이 알고 있으면 좋은 내용인 것 같아 오늘도 포스팅을 써본다🙋🏻♀️
주제 정의
Spring Security의 StrictHttpFirewall이 비표준 HTTP 메서드를 차단하는 원리와, 폐쇄망 서버에서 PROPFIND 요청이 간헐적으로 발생하는 이유를 분석한다.
단순히 에러를 끄는 방법이 아니라, 왜 이 에러가 발생하는지, Spring Security 내부에서 어떤 흐름으로 차단이 일어나는지를 중심으로 정리한다.

원전 / 공식 스펙 요약
HttpFirewall 인터페이스
Spring Security는 HttpFirewall이라는 인터페이스를 통해 요청 유효성 검사를 추상화한다.
public interface HttpFirewall {
FirewalledRequest getFirewalledRequest(HttpServletRequest request)
throws RequestRejectedException;
HttpServletResponse getFirewalledResponse(HttpServletResponse response);
}
구현체는 두 가지다.
| 구현체 | 설명 |
|---|---|
DefaultHttpFirewall |
최소한의 검사만 수행. 레거시 호환용 |
StrictHttpFirewall |
Spring Security 4.2.4+부터 기본값. 엄격한 검사 수행 |
StrictHttpFirewall의 검사 항목
StrictHttpFirewall은 요청이 필터 체인에 진입하기 전, 아래 항목을 순서대로 검사한다.

1. HTTP 메서드 검사
허용 목록에 없는 메서드는 즉시 차단한다.
// StrictHttpFirewall 내부 기본 허용 목록
private static final Set<String> ALLOW_ANY_HTTP_METHOD = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(
"DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"
))
);
PROPFIND, MKCOL, COPY 등 WebDAV 전용 메서드는 이 목록에 포함되지 않는다.
2. URL 정규화 검사
비정규화된 경로를 차단한다. 디렉터리 트래버설 공격 방지가 목적이다.
/normal/path → 허용
/path/../secret → 차단 (Path Traversal)
//double/slash → 차단
/path/%2F/encoded → 차단 (URL 인코딩 우회 시도)
3. 헤더 / 파라미터 CR·LF 검사
HTTP Response Splitting 공격을 방지하기 위해 헤더와 파라미터 값에 \r, \n이 포함되면 차단한다.
4. 호스트 헤더 검사
허용된 호스트 목록을 설정한 경우, 목록 외 호스트로의 요청을 차단한다.
PROPFIND와 WebDAV
PROPFIND는 HTTP/1.1을 확장한 WebDAV 프로토콜에서 정의하는 메서드다.
WebDAV는 웹 서버를 파일 시스템처럼 다룰 수 있게 하는 프로토콜로
HTTP를 기반으로 파일 생성·수정·삭제·이동이 가능하다.
WebDAV가 추가한 주요 메서드는 아래와 같다.
| 메서드 | 역할 |
|---|---|
PROPFIND |
리소스의 속성(메타데이터) 조회 |
PROPPATCH |
리소스 속성 수정 |
MKCOL |
컬렉션(디렉터리) 생성 |
COPY |
리소스 복사 |
MOVE |
리소스 이동 |
LOCK / UNLOCK |
리소스 잠금 |
이 중 PROPFIND는 클라이언트가 서버에 접근할 때 가장 먼저 보내는 탐색 요청이기 때문에
WebDAV를 사용하는 클라이언트라면 반드시 발생한다.
실무 해석 및 분석
요청 차단 흐름 상세 분석
실제 에러 스택을 기준으로 흐름을 따라가 본다.
at StrictHttpFirewall.rejectForbiddenHttpMethod(StrictHttpFirewall.java:531)
at StrictHttpFirewall.getFirewalledRequest(StrictHttpFirewall.java:508)
at FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
at FilterChainProxy.doFilter(FilterChainProxy.java:191)
at CompositeFilter$VirtualFilterChain.doFilter(CompositeFilter.java:113)
at HandlerMappingIntrospector.lambda$createCacheFilter$3(HandlerMappingIntrospector.java:195)
흐름을 단계별로 정리하면 다음과 같다.
HTTP 요청 수신
↓
FilterChainProxy.doFilter()
↓
FilterChainProxy.doFilterInternal()
↓
StrictHttpFirewall.getFirewalledRequest() ← 여기서 검사 시작
↓
rejectForbiddenHttpMethod() ← PROPFIND 감지
↓
RequestRejectedException 발생
↓
클라이언트 → 400 Bad Request 응답



mermaid를 사용중이 신 분은
아래 소스를 복사하여 사용하시면 흐름도를 깔끔하게 볼 수 있습니다.❤️
```mermaid
flowchart TD
A([🌐 HTTP 요청 수신]) --> B[FilterChainProxy.doFilter]
B --> C[FilterChainProxy.doFilterInternal]
C --> D[StrictHttpFirewall.getFirewalledRequest]
D --> E{HTTP 메서드\n허용 목록 검사}
E -- "❌ 비허용 메서드\nPROPFIND 등" --> F[rejectForbiddenHttpMethod]
F --> G[RequestRejectedException 발생]
G --> H([🔴 400 Bad Request 응답])
E -- "✅ 허용 메서드\nGET / POST 등" --> I{URL 정규화\n검사}
I -- "❌ 비정규화 경로\n/../ // 등" --> G
I -- "✅ 정상 경로" --> J{헤더·파라미터\nCR·LF 검사}
J -- "❌ CR·LF 포함" --> G
J -- "✅ 정상" --> K{호스트 헤더\n검사}
K -- "❌ 비허용 호스트" --> G
K -- "✅ 정상" --> L[FilterChain 진입]
L --> M[Security Filter들 순차 실행]
M --> N[DispatcherServlet]
N --> O[HandlerMapping → Controller]
O --> P([🟢 정상 응답])
style A fill:#4A90D9,color:#fff,stroke:none
style H fill:#E05C5C,color:#fff,stroke:none
style P fill:#5BAD72,color:#fff,stroke:none
style F fill:#E8A838,color:#fff,stroke:none
style G fill:#E05C5C,color:#fff,stroke:none
```
핵심은 컨트롤러는커녕 서블릿 필터 체인에도 진입하지 못한 채 차단된다는 점이다.
DispatcherServlet, HandlerMapping, @RequestMapping 어디에도 도달하지 않는다.
왜 폐쇄망 서버에서 간헐적으로 발생하는가?
외부 인터넷이 차단된 폐쇄망이라도, 내부 네트워크에는 WebDAV를 사용하는 클라이언트가 다수 존재한다.
간헐적으로 발생하는 이유는, 이 클라이언트들이 사용자의 명시적 행동 없이 자동으로 요청을 보내기 때문이다.

| 발생 주체 | 발생 조건 | 설명 |
|---|---|---|
| Windows 탐색기 | 네트워크 드라이브 연결 시 | 서버 URL을 탐색기에 입력하면 자동으로 PROPFIND 전송 |
| Microsoft Office | 파일 열기/저장 경로에 서버 URL 사용 시 | Word, Excel이 서버를 WebDAV 스토리지로 인식하고 탐색 |
| 보안 장비 (IDS/IPS) | 주기적 헬스체크 / 포트 스캐닝 시 | 비표준 메서드로 서버 응답 유형을 판별 |
| 내부 모니터링 도구 | 서버 상태 점검 시 | 일부 도구가 WebDAV 프로토콜 기반 점검을 수행 |
| 개발자 로컬 환경 | IDE나 REST 클라이언트 설정 오류 시 | IntelliJ HTTP Client, Postman 설정 실수로 발생하기도 함 |

이 로그는 위험한가?
결론부터 말하면, 위험하지 않다.
StrictHttpFirewall이 설계된 대로 동작하고 있다는 증거다.
서버는 허용하지 않은 메서드를 정상적으로 거부했고, 실제 비즈니스 로직에는 전혀 영향을 주지 않는다.
다만 두 가지 상황에서는 대응이 필요하다.
- 로그가 과도하게 쌓여 실제 장애 로그를 가리는 경우
- 내부 서비스가 WebDAV를 실제로 사용해야 하는 경우
DefaultHttpFirewall과의 차이
레거시 프로젝트에서 DefaultHttpFirewall을 사용 중이라면 이 에러가 발생하지 않는다.
DefaultHttpFirewall은 메서드 허용 목록 검사를 수행하지 않기 때문이다.
Spring Boot 3, Spring Security 6으로 마이그레이션하는 과정에서
StrictHttpFirewall이 기본값으로 바뀌면서 이런 로그가 새로 등장하는 경우가 많다.
고도화 프로젝트 진행 중이라면 이 지점을 눈여겨볼 필요가 있다.

결론 및 실무 팁
상황에 따라 세 가지 기준으로 대응을 선택한다.

Case 1. WebDAV가 전혀 필요 없고, 로그 양도 감당 가능한 경우 → 방치
보안 측면에서 정상 동작 중이다. 별도 조치 없이 모니터링만 유지한다.
Case 2. 로그만 억제하고 싶은 경우 → RequestRejectedHandler 등록
Spring Security 5.7부터 RequestRejectedHandler를 빈으로 등록하면 처리 방식을 커스텀할 수 있다.
기본 구현체인 HttpStatusRequestRejectedHandler는 400 응답만 내리고 스택 트레이스를 남기지 않는다.
@Configuration
public class SecurityConfig {
/**
* RequestRejectedException 발생 시 스택 트레이스 없이 400만 응답한다.
* Spring Security 5.7+ 기준
*/
@Bean
public RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}
}
로그 레벨만 조정하는 방법도 있다.
# application.properties
logging.level.org.springframework.security.web.firewall=ERROR
단, 이 방법은 다른 방화벽 관련 경고도 함께 억제하므로 주의한다.
Case 3. 내부 서비스가 WebDAV를 실제로 사용하는 경우 → 허용 메서드 명시적 추가
StrictHttpFirewall에 허용 메서드를 추가하고, WebSecurityCustomizer에 등록한다.
@Configuration
public class SecurityConfig {
/**
* PROPFIND 메서드를 허용 목록에 추가한다.
* WebDAV를 실제로 사용하는 경우에만 적용한다.
*/
@Bean
public HttpFirewall allowWebDavFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(
Arrays.asList(
"HEAD", "DELETE", "POST", "GET",
"OPTIONS", "PATCH", "PUT", "PROPFIND"
)
);
return firewall;
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return web -> web.httpFirewall(allowWebDavFirewall());
}
}
허용 메서드를 넓히면 공격 표면이 넓어진다.
PROPFIND 요청의 출처를 먼저 파악하고, 실제로 필요한 경우에만 적용한다.
📎 참고 링크