Path Matching
Path Matching
La API de Servlet expone la ruta completa de la petición como requestURI, y además la subdivide en varias partes: contextPath, servletPath y pathInfo, cuyos valores varían dependiendo de cómo esté mapeado el Servlet.
A partir de estos inputs, Spring MVC necesita determinar la ruta de búsqueda (lookup path) que se usará para hacer el mapping de los handlers.
Esta ruta debe excluir el contextPath y también cualquier prefijo del servletMapping, si aplica.
El servletPath y el pathInfo son decodificados (decoded), y eso hace que no se puedan comparar directamente con el requestURI completo para derivar el lookupPath, por lo que es necesario decodificar también el requestURI.
Sin embargo, esto trae sus propios problemas, porque el path puede contener caracteres reservados codificados como / o ;, que al ser decodificados pueden alterar la estructura del path —lo que también puede derivar en problemas de seguridad.
Además, los contenedores de Servlet (como Tomcat o Jetty) pueden normalizar el servletPath de diferentes maneras, lo cual hace aún más difícil realizar comparaciones como startsWith() contra el requestURI.
Por eso, lo más recomendable es no depender del servletPath, especialmente en mappings de tipo prefijo (prefix-based servletPath mapping).
Si el DispatcherServlet está mapeado como Servlet por defecto, es decir, con / o sin prefijo (/*), y el contenedor de Servlets es versión 4.0 o superior, entonces Spring MVC puede detectar el tipo de mapping y evitar completamente el uso de servletPath y pathInfo.
En un contenedor Servlet 3.1, se puede lograr un resultado equivalente configurando un UrlPathHelper con alwaysUseFullPath=true dentro del MVC config, usando la sección de Path Matching.
Afortunadamente, el mapping por defecto / es una buena elección.
Aun así, persiste un problema: el requestURI debe ser decodificado para poder compararse con los mappings de los controladores.
Esto también es indeseable, porque puede decodificar caracteres reservados que alteran la estructura del path.
Si no se espera que haya esos caracteres, se pueden rechazar (por ejemplo, como lo hace el HTTP firewall de Spring Security),
o se puede configurar UrlPathHelper con urlDecode=false, pero entonces los mappings de los controladores tendrán que coincidir con la ruta codificada, lo cual puede no funcionar bien en todos los casos.
Además, a veces el DispatcherServlet comparte el espacio de URL con otro Servlet, y en ese caso puede ser necesario mapearlo por prefijo.
Todos estos problemas se resuelven usando PathPatternParser y patrones parseados, como una alternativa al path matching basado en Strings con AntPathMatcher.
PathPatternParser está disponible desde Spring MVC 5.3, y viene habilitado por defecto a partir de Spring 6.0.
A diferencia de AntPathMatcher, que necesita que el lookupPath esté decodificado o que el mapping del controller esté codificado,
un PathPattern parseado hace match contra una representación parseada del path llamada RequestPath, segmento por segmento.
Esto permite decodificar y sanitizar cada segmento individualmente, sin alterar la estructura del path completo.
Además, los PathPattern parseados también soportan mappings por prefijo en el servletPath, siempre que se utilice un mapeo de Servlet y el prefijo sea simple (es decir, sin caracteres codificados).
Para más detalles sobre la sintaxis de patrones y su comparación, consulta la sección Pattern Comparison.
-
AntPathMatcher vs PathPatternParser
Vamos a ver un ejemplo práctico y comparativo de lo que habla la documentación: cómo se comporta Spring MVC al mapear rutas usando
AntPathMatcher(forma tradicional basada enString) versusPathPatternParser(forma moderna y recomendada desde Spring 5.3/6.0).
🎯 Escenario: Ruta con caracteres codificados
Supongamos que el cliente hace una petición HTTP a esta URL:
GET /api/user%2F123%2F es la codificación del carácter /, así que esta URL podría ser peligrosa o malinterpretada por el servidor si se decodifica incorrectamente.
🔁 Usando
AntPathMatcher(forma tradicional)Configuración:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { UrlPathHelper helper = new UrlPathHelper(); helper.setUrlDecode(true); // ← ¡Aquí se decodifica el path! configurer.setUrlPathHelper(helper); } }Controlador:
@RestController @RequestMapping("/api") public class UserController { @GetMapping("/user/{id}") public String getUser(@PathVariable String id) { return "User ID: " + id; } }Resultado:
- La URL
/api/user%2F123se decodifica como/api/user/123. - El controlador espera un solo
{id}, pero ahora ve123como parte de otro segmento. - ⚠️ Falla o da error 404 porque ahora Spring intenta buscar
/api/user/123, y no encuentra coincidencia.
✅ Usando
PathPatternParser(forma moderna y segura)Configuración:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configurePathMatch(PathMatchConfigurer configurer) { // Habilitar PathPatternParser (opcional desde Spring 6, por defecto activado) configurer.setPatternParser(new PathPatternParser()); }Controlador (mismo que antes):
@RestController @RequestMapping("/api") public class UserController { @GetMapping("/user/{id}") public String getUser(@PathVariable String id) { return "User ID: " + id; } }Resultado:
PathPatternParserparsea la URL segmentada, incluso si hay caracteres codificados.- La ruta
/api/user%2F123se mantiene segmentada correctamente, no intenta decodificar%2Fen/. - El controlador mapea correctamente a
id = user%2F123si así se espera. - ✅ Seguridad mejorada, menos ambigüedad.
🧠 Conclusión
Enfoque Decodifica %2FRiesgo de error Recomendado AntPathMatcher✅ Sí ⚠️ Alto ❌ No PathPatternParser❌ No (seguro) ✅ Bajo ✅ Sí - La URL
Source: https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-servlet/handlermapping-path.html