Spring MVC @PathVariable 被截断

Posted

技术标签:

【中文标题】Spring MVC @PathVariable 被截断【英文标题】:Spring MVC @PathVariable getting truncated 【发布时间】:2011-04-01 09:21:15 【问题描述】:

我有一个控制器提供 RESTful 访问信息:

@RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/blahName")
public ModelAndView getBlah(@PathVariable String blahName, HttpServletRequest request,
                            HttpServletResponse response) 

我遇到的问题是,如果我使用带有特殊字符的路径变量访问服务器,它会被截断。例如: http://localhost:8080/blah-server/blah/get/blah2010.08.19-02:25:47

参数blahName将为blah2010.08

但是,对 request.getRequestURI() 的调用包含传入的所有信息。

知道如何防止 Spring 截断 @PathVariable 吗?

【问题讨论】:

看来这个问题已经在 Spring 3.2-M2 中解决了:见Allow valid file extension paths for content negotiation to be specified 和its documentation。 【参考方案1】:

您面临的问题是由于 springdot (.) 作为 文件扩展名,如 .json 或 .xml 。因此,当 spring 尝试解析路径变量时,它会在遇到 uri 末尾的点 (.) 后简单地截断其余数据。 注意:只有在 uri 末尾保留路径变量时才会发生这种情况。

例如考虑 uri:https://localhost/example/gallery.df/link.ar

@RestController
public class CustomController 
    @GetMapping("/example/firstValue/secondValue")
    public void example(@PathVariable("firstValue") String firstValue,
      @PathVariable("secondValue") String secondValue) 
        // ...  
    

在上面的 url firstValue = "gallery.df" 和 secondValue="link" 中, .当路径变量被解释时被截断。

所以,为了防止这种情况,有两种可能的方法:

1.) 使用正则表达式映射

在映射的最后部分使用正则表达式

@GetMapping("/example/firstValue/secondValue:.+")   
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) 
    //...

通过使用 + ,我们表示点之后的任何值也将成为路径变量的一部分。

2.) 在@PathVariable 末尾添加一个斜杠

@GetMapping("/example/firstValue/secondValue/")
public void example(
  @PathVariable("firstValue") String firstValue,
  @PathVariable("secondValue") String secondValue) 
    //...

这将包含我们的第二个变量,以保护它免受 Spring 的默认行为。

3) 通过覆盖 Spring 的默认 webmvc 配置

Spring 提供了方法来覆盖通过使用注解导入的默认配置 @EnableWebMvc。我们可以通过声明我们自己的 DefaultAnnotationHandlerMapping 来自定义 Spring MVC 配置 bean 在应用程序上下文中并将其 useDefaultSuffixPattern 属性设置为 false。 示例:

@Configuration
public class CustomWebConfiguration extends WebMvcConfigurationSupport 

    @Bean
    public RequestMappingHandlerMapping 
      requestMappingHandlerMapping() 

        RequestMappingHandlerMapping handlerMapping
          = super.requestMappingHandlerMapping();
        handlerMapping.setUseSuffixPatternMatch(false);
        return handlerMapping;
    

请记住,覆盖此默认配置会影响所有 url。

注意:这里我们扩展了 WebMvcConfigurationSupport 类来覆盖默认方法。还有另一种方法可以通过实现 WebMvcConfigurer 接口来覆盖默认配置。 更多详情请阅读:https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

【讨论】:

【参考方案2】:

Spring 认为最后一个点后面的任何内容都是文件扩展名,例如 .json.xml,并将其截断以检索您的参数。

所以如果你有/blahName:

/param/param.json/param.xml/param.anything 将产生一个值为 param 的参数 /param.value.json/param.value.xml/param.value.anything 将产生一个值为 param.value 的参数

如果您按照建议将映射更改为 /blahName:.+,则任何点(包括最后一个点)都将被视为参数的一部分:

/param 将产生一个值为 param 的参数 /param.json 将产生一个值为 param.json 的参数 /param.xml 将产生一个值为 param.xml 的参数 /param.anything 将产生一个值为 param.anything 的参数 /param.value.json 将产生一个值为 param.value.json 的参数 ...

如果您不关心扩展识别,可以通过覆盖 mvc:annotation-driven automagic 来禁用它:

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
    <property name="useSuffixPatternMatch" value="false"/>
</bean>

所以,如果你有/blahName

/param/param.json/param.xml/param.anything 将产生一个值为 param 的参数 /param.value.json/param.value.xml/param.value.anything 将产生一个值为 param.value 的参数

注意:与默认配置的区别仅在您具有/something.blahName 之类的映射时才可见。见Resthub project issue。

如果你想保持扩展管理,从 Spring 3.2 开始你还可以设置 RequestMappingHandlerMapping bean 的 useRegisteredSuffixPatternMatch 属性,以保持 suffixPattern 识别激活但仅限于注册扩展。

这里你只定义了 json 和 xml 扩展:

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="contentNegotiationManager" ref="contentNegotiationManager"/>
    <property name="useRegisteredSuffixPatternMatch" value="true"/>
</bean>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="favorPathExtension" value="false"/>
    <property name="favorParameter" value="true"/>
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

请注意,mvc:annotation-driven 现在接受 contentNegotiation 选项以提供自定义 bean,但 RequestMappingHandlerMapping 的属性必须更改为 true(默认为 false)(参见 https://jira.springsource.org/browse/SPR-7632)。

因此,您仍然必须覆盖所有 mvc:annotation 驱动的配置。我打开了一张 Spring 的票,要求自定义 RequestMappingHandlerMapping:https://jira.springsource.org/browse/SPR-11253。如果您有兴趣,请投票。

在覆盖时,还要小心考虑自定义执行管理覆盖。否则,您的所有自定义异常映射都将失败。您将不得不重用带有列表 bean 的 messageCoverters:

<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean" />

<util:list id="messageConverters">
    <bean class="your.custom.message.converter.IfAny"></bean>
    <bean class="org.springframework.http.converter.ByteArrayHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.StringHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.SourceHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter"></bean>
    <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"></bean>
</util:list>

<bean name="exceptionHandlerExceptionResolver"
      class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver">
    <property name="order" value="0"/>
    <property name="messageConverters" ref="messageConverters"/>
</bean>

<bean name="handlerAdapter"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <property name="webBindingInitializer">
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="conversionService" ref="conversionService" />
            <property name="validator" ref="validator" />
        </bean>
    </property>
    <property name="messageConverters" ref="messageConverters"/>
</bean>

<bean id="handlerMapping"
      class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
</bean>

我在我参与的开源项目 Resthub 中实施了一组针对这些主题的测试:请参阅 https://github.com/resthub/resthub-spring-stack/pull/219/files 和 https://github.com/resthub/resthub-spring-stack/issues/217

【讨论】:

【参考方案3】:

防止 Spring MVC @PathVariable 被截断的首选解决方案是在路径变量的末尾添加斜杠。

例如:

@RequestMapping(value ="/email/email/")

因此,请求将如下所示:

http://localhost:8080/api/email/test@test.com/

【讨论】:

【参考方案4】:

我通过这个 hack 解决了

1) 在@PathVariable 中添加HttpServletRequest,如下所示

 @PathVariable("requestParam") String requestParam, HttpServletRequest request) throws Exception  

2)在请求中直接获取URL(此级别不截断)

request.getPathInfo() 

Spring MVC @PathVariable with dot (.) is getting truncated

【讨论】:

【参考方案5】:

添加“:.+”对我有用,但直到我删除了外部大括号。

value = "/username/id:.+" 没用

value = "/username/id:.+" 有效

希望我能帮助到某人:]

【讨论】:

【参考方案6】:

如果您确定您的文本与任何默认扩展名都不匹配,您可以使用以下代码:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter 

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) 
        configurer.setUseRegisteredSuffixPatternMatch(true);
    

【讨论】:

【参考方案7】:

防止截断的基于 Java 的配置解决方案(使用未弃用的类):

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
public class PolRepWebConfig extends WebMvcConfigurationSupport 

    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() 
        final RequestMappingHandlerMapping handlerMapping = super
                .requestMappingHandlerMapping();
        // disable the truncation after .
        handlerMapping.setUseSuffixPatternMatch(false);
        // disable the truncation after ;
        handlerMapping.setRemoveSemicolonContent(false);
        return handlerMapping;
    

Source: http://www.javacodegeeks.com/2013/01/spring-mvc-customizing-requestmappinghandlermapping.html

更新:

当我使用上述方法时,我意识到 Spring Boot 自动配置存在一些问题(一些自动配置没有生效)。

相反,我开始使用BeanPostProcessor 方法。它似乎工作得更好。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor 
    private static final Logger logger = LoggerFactory
            .getLogger(MyBeanPostProcessor.class);

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException 
        return bean;
    

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException 
        if (bean instanceof RequestMappingHandlerMapping) 
            setRemoveSemicolonContent((RequestMappingHandlerMapping) bean,
                    beanName);
            setUseSuffixPatternMatch((RequestMappingHandlerMapping) bean,
                    beanName);
        
        return bean;
    

    private void setRemoveSemicolonContent(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) 
        logger.info(
                "Setting 'RemoveSemicolonContent' on 'RequestMappingHandlerMapping'-bean to false. Bean name: ",
                beanName);
        requestMappingHandlerMapping.setRemoveSemicolonContent(false);
    

    private void setUseSuffixPatternMatch(
            RequestMappingHandlerMapping requestMappingHandlerMapping,
            String beanName) 
        logger.info(
                "Setting 'UseSuffixPatternMatch' on 'RequestMappingHandlerMapping'-bean to false. Bean name: ",
                beanName);
        requestMappingHandlerMapping.setUseSuffixPatternMatch(false);
    

Inspired from: http://ronaldxq.blogspot.com/2014/10/spring-mvc-setting-alwaysusefullpath-on.html

【讨论】:

【参考方案8】:

使用正确的 Java 配置类:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter


    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
    
        configurer.favorPathExtension(false);
    

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer)
    
        configurer.setUseSuffixPatternMatch(false);
    

【讨论】:

这对我很有用。在 Tomcat Spring 版本 4.3.14 上运行【参考方案9】:
//in your xml dispatcher  add this property to your default annotation mapper bean as follow
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">
    <property name="alwaysUseFullPath" value="true"></property>
</bean>       

【讨论】:

【参考方案10】:

如果您可以编辑请求发送到的地址,简单的解决方法是在它们的尾部添加一个斜杠(以及在 @RequestMapping 值中):

/path/variable/

所以映射看起来像:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/blahName/")

另见Spring MVC @PathVariable with dot (.) is getting truncated。

【讨论】:

【参考方案11】:

仅当参数位于 URL 的最后部分时,才会存在文件扩展名问题。改变

@RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/blahName")

@RequestMapping(
   method = RequestMethod.GET, value = Routes.BLAH_GET + "/blahName/safe")

一切都会好起来的-

【讨论】:

【参考方案12】:

我也遇到了同样的问题,将属性设置为 false 也对我没有帮助。但是,the API says:

请注意,包含“.xxx”后缀或以“/”结尾的路径已经 在任何情况下都不会使用默认的后缀模式进行转换。

我尝试在我的 RESTful URL 中添加“/end”,问题就消失了。我对解决方案不满意,但它确实有效。

顺便说一句,我不知道 Spring 设计者在添加这个“功能”然后默认打开它时是怎么想的。恕我直言,它应该被删除。

【讨论】:

我同意。我最近被这个咬了。【参考方案13】:

我刚刚遇到了这个问题,这里的解决方案通常没有像我预期的那样工作。

我建议使用 SpEL 表达式和多个映射,例如

@RequestMapping(method = RequestMethod.GET, 
    value = Routes.BLAH_GET + "/blahName:.+", 
             Routes.BLAH_GET + "/blahName/")

【讨论】:

【参考方案14】:

最后一个点之后的所有内容都被解释为文件扩展名并默认切断。 在您的 spring 配置 xml 中,您可以添加 DefaultAnnotationHandlerMapping 并将 useDefaultSuffixPattern 设置为 false(默认为 true)。

所以打开你的 spring xml mvc-config.xml(或者不管它怎么叫)并添加

<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
    <property name="useDefaultSuffixPattern" value="false" />
</bean>

现在您的@PathVariable blahName(以及所有其他)应该包含全名,包括所有点。

编辑:这是link to the spring api

【讨论】:

我没试过,但others claim 如果适用,你还需要删除&lt;mvc:annotation-driven /&gt;【参考方案15】:

这可能与SPR-6164 密切相关。简而言之,该框架尝试对 URI 解释应用一些智能,删除它认为的文件扩展名。这会将blah2010.08.19-02:25:47 转换为blah2010.08,因为它认为.19-02:25:47 是一个文件扩展名。

如链接问题中所述,您可以通过在应用上下文中声明自己的DefaultAnnotationHandlerMapping bean 并将其useDefaultSuffixPattern 属性设置为false 来禁用此行为。这将覆盖默认行为,并停止它骚扰您的数据。

【讨论】:

默认开启基于扩展的内容协商似乎是一个奇怪的选择。在实践中,有多少系统真正以不同的格式公开相同的资源? 我早上试过了,但路径变量仍然被截断。 +1 是一个很好的答案,也可以使用“骚扰你的数据”这个短语 对于 Spring 3.1 用户 - 如果您使用的是新的 RequestMappingHandlerMapping,则要设置的属性是 useSuffixPatternMatch(也适用于 false)。 @Ted:链接的问题提到,他们希望在 3.2 中添加更多控制,因此不必是全有或全无。 在 Spring 4.2 中这更容易配置。我们使用 Java 配置类并扩展 WebMvcConfigurationSupport,它提供了一个简单的钩子:public void configurePathMatch(PathMatchConfigurer configurer) - 只需覆盖它并设置匹配您喜欢的路径。【参考方案16】:

@RequestMapping 参数尝试正则表达式:

RequestMapping(method = RequestMethod.GET, value = Routes.BLAH_GET + "/blahName:.+")

【讨论】:

感谢您的回答,这帮助我解决了用户名以某种方式被修剪的情况.. (-: 'useDefaultSuffixPattern' 的另一个选项不是一个选项,因为我们使用的是 @Configuration spring 类的 XML。 这行得通,但是正则表达式中冒号的意义是什么? 诺亚,好久没用这个了,不过我觉得冒号把正则表达式和参数名隔开来绑定。 我们遇到了类似的问题 /item/user@abc.com,@ 被截断后的任何内容,通过添加另一个斜杠 /item/user@abc.com/ 解决了这个问题

以上是关于Spring MVC @PathVariable 被截断的主要内容,如果未能解决你的问题,请参考以下文章

Spring MVC 正在删除 @PathVariable

Spring MVC @PathVariable 被截断

Spring MVC中的多个@PathVariable

带有 @PathVariable 的 Spring MVC 带注释的控制器接口

源码篇Spring MVC多种请求入参处理方式都在这了(@RequestParam@PathVariable@RequestBodyMapJavaModelRequest基础类型)

Spring MVC 基础篇 2