Path Matching
Spring MVC는 요청을 적절한 핸들러(컨트롤러)로 라우팅하기 위해 경로(Path) 매칭을 수행합니다.
그러나, 경로 매칭 과정에서 Servlet API의 경로 처리 방식과 URL 디코딩 문제 등으로 인해 다양한 복잡성이 발생할 수 있습니다.
이를 해결하기 위해 Spring MVC는 여러 가지 전략을 제공합니다.
1. Servlet API가 제공하는 경로 정보
Servlet API는 요청 경로를 여러 부분으로 나누어 제공합니다.
경로 유형 | 설명 | 예제 (/myapp/api/users/123 ) |
---|---|---|
requestURI | 전체 요청 경로 | /myapp/api/users/123 |
contextPath | 애플리케이션 컨텍스트 경로 (기본적으로 web.xml 에서 설정) |
/myapp |
servletPath | 서블릿 매핑 경로 | /api |
pathInfo | 추가적인 경로 정보 | /users/123 |
📌 즉, Spring MVC는 요청을 핸들러에 매핑할 때 contextPath
와 servletPath
를 제외한 lookupPath
를 계산해야 합니다.
2. Path Decoding 문제와 보안 이슈
경로를 비교하기 위해 requestURI에서 contextPath 및 servletPath를 제외한 lookupPath를 추출해야 합니다.
그러나 경로를 디코딩할 때 몇 가지 문제가 발생할 수 있습니다.
🚨 문제 1: 디코딩된 경로 비교 불가능
servletPath
와pathInfo
는 디코딩된 상태에서 제공되지만,requestURI
는 인코딩된 상태로 존재합니다.- 따라서
requestURI
를 디코딩해야만 비교할 수 있습니다. - 하지만 이 과정에서 예약된 문자(예:
/
,;
)가 디코딩되어 경로 구조가 변경될 위험이 있습니다.
📌 예제:
- 원본 요청:
/api%2Fusers/123
(인코딩된/api/users/123
):일반적으로 url에서 / 의 용도는 Separator(구분 기호)입니다. 그러나 이 예제에서 api와users 사이에 url의 Separator / 가 아닌 문자 /를 사용할 때 /가 %2F로 인코딩됩니다. - 디코딩 후:
/api/users/123
/
문자가 디코딩되면서 경로가 달라져 보안 문제가 발생할 수 있음
🚨 문제 2: Servlet 컨테이너마다 servletPath 처리 방식이 다름
- 일부 서블릿 컨테이너는
servletPath
를 정규화(Normalize)하여 제공하지만, 그렇지 않은 경우도 있음 - 결과적으로
requestURI.startsWith(servletPath)
비교가 불가능한 경우가 있음
📌 결론:
servletPath를 직접 사용하는 것이 비추천되며, DispatcherServlet이 servletPath 없이 동작하는 방식이 더 바람직함.
3. DispatcherServlet이 "/" 매핑을 사용할 경우
만약 DispatcherServlet이 /
또는 /*
로 매핑되어 있다면,
Spring MVC는 서블릿 4.0+ 환경에서 servletPath 및 pathInfo를 완전히 무시할 수 있습니다.
📌 즉, 서블릿 컨테이너가 4.0 이상이면, Spring MVC가 servletPath와 pathInfo를 신경 쓰지 않고 requestURI만으로 처리 가능!
서블릿 3.1 환경에서는 어떻게 해결?
UrlPathHelper
를 사용하여alwaysUseFullPath=true
설정하면 같은 효과를 얻을 수 있음
@Bean
public UrlPathHelper urlPathHelper() {
UrlPathHelper helper = new UrlPathHelper();
helper.setAlwaysUseFullPath(true);
return helper;
}
4. Path Decoding의 보안 문제 해결
경로를 매칭할 때 디코딩된 경로를 비교해야 하지만, 디코딩 시 보안 문제가 발생할 수 있습니다.
🚨 해결 방법 1: Spring Security의 HTTP Firewall을 사용하여 예약된 문자 차단
- Spring Security를 활용하면
/
등의 예약된 문자가 포함된 요청을 거부할 수 있음
@Bean
public HttpFirewall httpFirewall() {
DefaultHttpFirewall firewall = new DefaultHttpFirewall();
return firewall;
}
🚨 해결 방법 2: UrlPathHelper에서 URL 디코딩 비활성화
- 디코딩된 경로 비교를 하지 않고, 인코딩된 상태 그대로 비교할 수도 있음
@Bean
public UrlPathHelper urlPathHelper() {
UrlPathHelper helper = new UrlPathHelper();
helper.setUrlDecode(false); // URL 디코딩 비활성화
return helper;
}
📌 주의:
컨트롤러의 요청 매핑도 인코딩된 상태로 작성해야 할 수도 있음.
5. PathPatternParser 사용 (Spring MVC 5.3+)
Spring MVC 5.3부터는 기존의 AntPathMatcher
대신 PathPatternParser
를 사용할 수 있습니다.
Spring MVC 6.0부터는 기본적으로 PathPatternParser
가 활성화됩니다.
📌 기존 방식: AntPathMatcher
lookupPath
를 디코딩하거나 컨트롤러 매핑을 인코딩해야 경로 비교 가능
📌 새로운 방식: PathPatternParser
- 경로를
RequestPath
객체로 변환 후 비교 - 경로를 한 개의 세그먼트(segment) 단위로 개별 디코딩하여 비교
- 보안 이슈 없이 경로 구조 유지 가능
✅ PathPatternParser 사용 방법
@Bean
public WebMvcConfigurer configurer() {
return new WebMvcConfigurer() {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setPatternParser(new PathPatternParser());
}
};
}
📌 이점:
- 보안 문제 해결:
"/api%2Fusers/123"
을 안전하게 매칭 가능 - URL 매칭 정확성 증가:
lookupPath
디코딩 불필요 - 서블릿 매핑(prefix mapping) 지원: 서블릿 경로가 포함된 URL도 안전하게 처리 가능
🚀 Summary
1️⃣ Servlet API의 경로 처리 방식
requestURI
,contextPath
,servletPath
,pathInfo
로 나누어져 있음- Spring MVC는
lookupPath
를 생성해야 함
2️⃣ 경로 비교 시 발생하는 문제점
servletPath
가 컨테이너마다 다르게 정규화됨requestURI
를 디코딩할 때 보안 문제가 발생할 수 있음
3️⃣ 해결 방법
- DispatcherServlet을
/
로 매핑하면 servletPath를 사용하지 않도록 할 수 있음 - Spring Security HTTP Firewall을 이용해 보안 강화
- UrlPathHelper에서 URL 디코딩을 비활성화 가능 (
urlDecode=false
)
4️⃣ Spring MVC 5.3+의 PathPatternParser
사용
- 기존의
AntPathMatcher
보다 안전하고 정확한 경로 매칭 - 경로 세그먼트 단위로 개별 디코딩하여 보안 문제 해결
- Spring MVC 6.0부터 기본 활성화
📌 결론:
Spring MVC 6.0 이상에서는 PathPatternParser
를 사용하는 것이 가장 안전하고 추천되는 방식입니다! 🚀
출처 : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-path.html