当前位置: 首页 > 知识库问答 >
问题:

Spring 引导@PathVariable不能包含斜杠/分号/百分比字符

南门正祥
2023-03-14

我有一个REST API,其中包含spline-引导2.5.5和spline-Security 5.5.2。


但是当一些特殊(但在我的域上下文中有效)字符作为路径变量传递时,请求失败。
三个失败的特殊字符是:斜杠(/)、分号(;)和百分比(%)。

让我们举一个非常简单的例子:

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1")
public class Controller {

    @GetMapping("echo/{value}")
    public ResponseEntity<String> echo(@PathVariable("value") String value) {
        return ResponseEntity
                .status(HttpStatus.OK)
                .contentType(MediaType.TEXT_XML)
                .body(value);
    }

}

对于值“你好/那里”,我发送GET /api/v1/echo/hello/there,我收到:

<!doctype html><html lang="en"><head><title>HTTP Status 400 – Bad Request</title><style type="text/css">body {font-family:Tahoma,Arial,sans-serif;} h1, h2, h3, b {color:white;background-color:#525D76;} h1 {font-size:22px;} h2 {font-size:16px;} h3 {font-size:14px;} p {font-size:12px;} a {color:black;} .line {height:1px;background-color:#525D76;border:none;}</style></head><body><h1>HTTP Status 400 – Bad Request</h1><hr class="line" /><p><b>Type</b> Status Report</p><p><b>Message</b> Invalid URI: noSlash</p><p><b>Description</b> The server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).</p><hr class="line" /><h3>Apache Tomcat/9.0.53</h3></body></html>

对于值“hello;there”,我发送GET/api/v1/echo/hello;在那里,我收到:

{
  "stackTrace": [
    {
      "classLoaderName": "app",
      "methodName": "handleAccessDeniedException",
      "fileName": "ExceptionTranslationFilter.java",
      "lineNumber": 194,
      "nativeMethod": false,
      "className": "org.springframework.security.web.access.ExceptionTranslationFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "handleSpringSecurityException",
      "fileName": "ExceptionTranslationFilter.java",
      "lineNumber": 173,
      "nativeMethod": false,
      "className": "org.springframework.security.web.access.ExceptionTranslationFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ExceptionTranslationFilter.java",
      "lineNumber": 142,
      "nativeMethod": false,
      "className": "org.springframework.security.web.access.ExceptionTranslationFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ExceptionTranslationFilter.java",
      "lineNumber": 115,
      "nativeMethod": false,
      "className": "org.springframework.security.web.access.ExceptionTranslationFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "SessionManagementFilter.java",
      "lineNumber": 126,
      "nativeMethod": false,
      "className": "org.springframework.security.web.session.SessionManagementFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "SessionManagementFilter.java",
      "lineNumber": 81,
      "nativeMethod": false,
      "className": "org.springframework.security.web.session.SessionManagementFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "AnonymousAuthenticationFilter.java",
      "lineNumber": 105,
      "nativeMethod": false,
      "className": "org.springframework.security.web.authentication.AnonymousAuthenticationFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "SecurityContextHolderAwareRequestFilter.java",
      "lineNumber": 149,
      "nativeMethod": false,
      "className": "org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "RequestCacheAwareFilter.java",
      "lineNumber": 63,
      "nativeMethod": false,
      "className": "org.springframework.security.web.savedrequest.RequestCacheAwareFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "LogoutFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.security.web.authentication.logout.LogoutFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "LogoutFilter.java",
      "lineNumber": 89,
      "nativeMethod": false,
      "className": "org.springframework.security.web.authentication.logout.LogoutFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "SecurityContextPersistenceFilter.java",
      "lineNumber": 110,
      "nativeMethod": false,
      "className": "org.springframework.security.web.context.SecurityContextPersistenceFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "SecurityContextPersistenceFilter.java",
      "lineNumber": 80,
      "nativeMethod": false,
      "className": "org.springframework.security.web.context.SecurityContextPersistenceFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ChannelProcessingFilter.java",
      "lineNumber": 133,
      "nativeMethod": false,
      "className": "org.springframework.security.web.access.channel.ChannelProcessingFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 336,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy$VirtualFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilterInternal",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 211,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "FilterChainProxy.java",
      "lineNumber": 183,
      "nativeMethod": false,
      "className": "org.springframework.security.web.FilterChainProxy"
    },
    {
      "classLoaderName": "app",
      "methodName": "invokeDelegate",
      "fileName": "DelegatingFilterProxy.java",
      "lineNumber": 358,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.DelegatingFilterProxy"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "DelegatingFilterProxy.java",
      "lineNumber": 271,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.DelegatingFilterProxy"
    },
    {
      "classLoaderName": "app",
      "methodName": "internalDoFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 189,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 162,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilterInternal",
      "fileName": "RequestContextFilter.java",
      "lineNumber": 100,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.RequestContextFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 119,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "internalDoFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 189,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 162,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "internalDoFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 189,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 162,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "OncePerRequestFilter.java",
      "lineNumber": 103,
      "nativeMethod": false,
      "className": "org.springframework.web.filter.OncePerRequestFilter"
    },
    {
      "classLoaderName": "app",
      "methodName": "internalDoFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 189,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "doFilter",
      "fileName": "ApplicationFilterChain.java",
      "lineNumber": 162,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationFilterChain"
    },
    {
      "classLoaderName": "app",
      "methodName": "invoke",
      "fileName": "ApplicationDispatcher.java",
      "lineNumber": 711,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationDispatcher"
    },
    {
      "classLoaderName": "app",
      "methodName": "processRequest",
      "fileName": "ApplicationDispatcher.java",
      "lineNumber": 461,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationDispatcher"
    },
    {
      "classLoaderName": "app",
      "methodName": "doForward",
      "fileName": "ApplicationDispatcher.java",
      "lineNumber": 385,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationDispatcher"
    },
    {
      "classLoaderName": "app",
      "methodName": "forward",
      "fileName": "ApplicationDispatcher.java",
      "lineNumber": 313,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.ApplicationDispatcher"
    },
    {
      "classLoaderName": "app",
      "methodName": "custom",
      "fileName": "StandardHostValve.java",
      "lineNumber": 403,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.StandardHostValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "status",
      "fileName": "StandardHostValve.java",
      "lineNumber": 249,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.StandardHostValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "throwable",
      "fileName": "StandardHostValve.java",
      "lineNumber": 344,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.StandardHostValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "invoke",
      "fileName": "StandardHostValve.java",
      "lineNumber": 169,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.StandardHostValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "invoke",
      "fileName": "ErrorReportValve.java",
      "lineNumber": 92,
      "nativeMethod": false,
      "className": "org.apache.catalina.valves.ErrorReportValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "invoke",
      "fileName": "StandardEngineValve.java",
      "lineNumber": 78,
      "nativeMethod": false,
      "className": "org.apache.catalina.core.StandardEngineValve"
    },
    {
      "classLoaderName": "app",
      "methodName": "service",
      "fileName": "CoyoteAdapter.java",
      "lineNumber": 357,
      "nativeMethod": false,
      "className": "org.apache.catalina.connector.CoyoteAdapter"
    },
    {
      "classLoaderName": "app",
      "methodName": "service",
      "fileName": "Http11Processor.java",
      "lineNumber": 382,
      "nativeMethod": false,
      "className": "org.apache.coyote.http11.Http11Processor"
    },
    {
      "classLoaderName": "app",
      "methodName": "process",
      "fileName": "AbstractProcessorLight.java",
      "lineNumber": 65,
      "nativeMethod": false,
      "className": "org.apache.coyote.AbstractProcessorLight"
    },
    {
      "classLoaderName": "app",
      "methodName": "process",
      "fileName": "AbstractProtocol.java",
      "lineNumber": 893,
      "nativeMethod": false,
      "className": "org.apache.coyote.AbstractProtocol$ConnectionHandler"
    },
    {
      "classLoaderName": "app",
      "methodName": "doRun",
      "fileName": "NioEndpoint.java",
      "lineNumber": 1726,
      "nativeMethod": false,
      "className": "org.apache.tomcat.util.net.NioEndpoint$SocketProcessor"
    },
    {
      "classLoaderName": "app",
      "methodName": "run",
      "fileName": "SocketProcessorBase.java",
      "lineNumber": 49,
      "nativeMethod": false,
      "className": "org.apache.tomcat.util.net.SocketProcessorBase"
    },
    {
      "classLoaderName": "app",
      "methodName": "runWorker",
      "fileName": "ThreadPoolExecutor.java",
      "lineNumber": 1191,
      "nativeMethod": false,
      "className": "org.apache.tomcat.util.threads.ThreadPoolExecutor"
    },
    {
      "classLoaderName": "app",
      "methodName": "run",
      "fileName": "ThreadPoolExecutor.java",
      "lineNumber": 659,
      "nativeMethod": false,
      "className": "org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker"
    },
    {
      "classLoaderName": "app",
      "methodName": "run",
      "fileName": "TaskThread.java",
      "lineNumber": 61,
      "nativeMethod": false,
      "className": "org.apache.tomcat.util.threads.TaskThread$WrappingRunnable"
    },
    {
      "moduleName": "java.base",
      "moduleVersion": "17.0.2",
      "methodName": "run",
      "fileName": "Thread.java",
      "lineNumber": 833,
      "nativeMethod": false,
      "className": "java.lang.Thread"
    }
  ],
  "type": "about:blank",
  "title": "Unauthorized",
  "status": "UNAUTHORIZED",
  "detail": "Full authentication is required to access this resource",
  "message": "Unauthorized: Full authentication is required to access this resource",
  "localizedMessage": "Unauthorized: Full authentication is required to access this resource"
}

对于值“hello%there”,我在那里发送GET/api/v1/echo/hello%,并收到与分号相同的结果。

任何其他特殊字符似乎都被 Spring 正确解码,但不是这 3 个。

我错过了什么吗
有没有什么好的方法来实现这一点,而不必告诉spring“嘿,不要解码路径变量,我会自己做”,也不必搞乱安全配置(如中所述https://www.baeldung.com/spring-slash-character-in-url) ?

共有3个答案

西门经国
2023-03-14

您是否尝试在特殊字符前使用< code>\或< code>`?

类似于hello\;那里

常智勇
2023-03-14

为了达到你的要求,你将不得不在tomcat中进行一些深度配置和/或一些自定义重写规则。所有这些都很容易适得其反,因为在应用程序的uri匹配器上造成故障,甚至更糟的是造成安全故障。

最简单的方法是将控制器从期望包含特殊字符的字符串作为@PathVariable转换为具有@RequestParam的查询参数。

因此,而不是使用

    @GetMapping("echo/{value}")
    public ResponseEntity<String> echo(@PathVariable("value") String value) {
        return ResponseEntity
                .status(HttpStatus.OK)
                .contentType(MediaType.TEXT_XML)
                .body(value);
    }

你可能会变成

   @GetMapping("echo/")
    public ResponseEntity<String> echo(@RequestParam("value") String value) {
        return ResponseEntity
                .status(HttpStatus.OK)
                .contentType(MediaType.TEXT_XML)
                .body(value);
    }

对于值“hello/there ”,我发送GET /api/v1/echo/hello/there

然后尝试使用 GET /api/v1/echo?value=hello/there

淳于恺
2023-03-14

对于Getmapping,您可以选择这种方式。

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/api/v1")
public class Controller {

    @GetMapping("echo/**")
    public ResponseEntity<String> echo(HttpServletRequest request) {
        String fullUrl = request.getRequestURL().toString();
        String url = fullUrl.split("/echo/")[1];
        System.out.println(url);
        return ResponseEntity
                .status(HttpStatus.OK)
                .contentType(MediaType.TEXT_XML)
                .body(url);
    }

}
 类似资料:
  • SWIG文档对这两个指令解释如下: > :“SWIG提供了另一个带有指令的文件包含指令。的目的是从另一个SWIG接口文件或头文件收集某些信息,而不实际生成任何包装代码。此类信息通常包括类型声明(例如,typedef)以及可能用作接口中类声明基类的C类。" 我的问题是这两个指令之间有什么区别,使用它们的利弊是什么? 顺便说一下,我只是想了解一些背景信息。我有一个简单的C-python扩展,当我使用上

  • 我已经阅读了这个类似问题的答案:YAML中的字符串是否需要引号? 然而,对于是否可以用包含正斜杠的字符串转义引号,没有答案。 例如,我们是否需要将引号添加到以下eslint规则“react/no deprecated”:off?

  • SpringMVC控制器中@PathVariable有问题。每当我传递包含plus('+')的字符串时,plus就会被空格替换。对参数进行编码没有帮助。 例如,如果我请求url myapp/resend-validation/my+mail@gmail.com,我在变量中得到“mymail@gmail.com”。在请求myapp/resend-validation/my%2bmail@gmail.

  • 我有一个shell脚本(我们称之为 ),它以下列形式输出数据,但不将其保存到文件中: 我想在外壳脚本中使用这些值作为环境变量。我目前正在使用以下shell代码执行此操作: 这非常有用,除非< code>=右边的值包含空格。例如: 我在<code>product.sh</code>中尝试了两种不同的方法: < li >用引号将值括起来(< code > FOO = " split value " )

  • 问题内容: 这让我发疯了,所以请您帮忙… 我有一个Java字符串,我想用单引号替换所有反斜杠双引号序列,即使我逃避了我认为必要的替换命令,该命令也不会对字符串产生任何影响。 感谢任何建议。 谢谢。 问题答案: 在Java中,字符串是不可变的。您对字符串执行的任何操作都会产生新的对象。操作后,您需要重新分配值。以下内容可能会对您有所帮助。

  • null Groovy:找不到四位十六进制字符代码