当前使用的spring boot版本

image

当spring boot版本\<2.3.1时,Spring 可以直接支持.. / ,这时我们可以使用 . ./ 的归一化特性通过 /static/ .. /api 访问接口 /api ,许多情况下我们可以利用这个 trick 满足开发者设定的白名单。

我当时的环境显然不在这个特性的适用范围之内,但是我却仍然能利用 . ./ 构造白名单开头的 url 访问接口,经过我的测试,我发现这样的特性有一个潜在的适用条件,只有在 application.properties中配置了 server.servlet.context- path 才能成功。比如我们设置 server.servlet.context-path=/demo ,这时我们可以通过访问 /static/ . /demo/hello 访问 hello 接口

image

但如果我们没有server.servlet.context-path或者直接配置为/,我们却不能通过访问/static/../hello访问hello接口:

image-20260105152155776

Spring 是如何解析我们的 url

Spring 的路由转发其实是一个非常复杂的过程,也有许多讲的非常好的文章分析了具体的访问流程

https://blog.csdn.net/m0_45406092/article/details/115423861

image-20260105152254223

简单来说从浏览器到 Controller 的访问流程如下:

浏览器请求:GET http: / localhost:8080/demo/hello
↓
Tomcat(Servlet 容器)
↓
DispatcherServlet(Spring MVC 前端控制器)
↓
HandlerMapping(根据 URL 寻找匹配的 Controller 方法)
↓
HandlerAdapter(调用 Controller)
↓
Controller 方法执行(HelloController.hello())
↓
返回 ModelAndView → 渲染视图 / JSON 输出

首先 Tomcat 作为 Servlet 容器,接收到请求 /demo/hello ,它会解析出以下三部分:

名称含义context-path \= /demo
contextPathWeb 应用部署路径,通常来自配置/demo
servletPath匹配到的 Servlet 的路径前缀/hello
pathInfoServlet 映射后的剩余路径(可选)null

Tomcat 决定将此请求分发给某个 Servlet ,在 Spring Boot 中, DispatcherServlet 是唯一的前端控制器 Servlet ,所有请求都会交给 DispatcherServlet 处理,它会接管请求进行统一的分派;接下来它会从 HttpServletRequest 提取出 Spring 内部使用的路径,即 LookupPath,无论是否配置contextPath ,Spring 内部的匹配路径都是去掉 contextPath 后的路径,即 /hello

获取 LookupPath 后, HandlerMapping 会匹配 Controller 方法,在这里也就是:

public class HelloController {
@GetMapping("/hello")
public String hello() {
return "hello fushuling";
}
}

匹配的原则是HTTP 方法一致、LookupPath 一致( /hello )且其他条件一致(headers、params、produces 等 ) 。 然 后 DispatcherServlet 会 调 用 对 应 的 HandlerAdapter ( 通 常 是RequestMappingHandlerAdapter ),完成返回值处理(视图渲染或 JSON 输出)、参数解析( @RequestParam, @PathVariable, @RequestBody 等 ) 和 方 法 执 行 ; 最 后 , 我 们 才 终 于 执 行 到Controller ,在浏览器中返回 hello fushuling

调试Spring

分析上面的流程,既然当我们不配置上下文时, /static/ . /hello 的访问结果为404,很显然是因为LookupPath 不正确,导致我们没有找到 Spring 内部使用的路径(pathWithinApplication) 从而匹配到/hello

在 getHandlerInternal 打 一 个 断 点 , 当 我 们 配 置 了 上 下 文 /demo 之 后 访 问/static/ .. /demo/hello ,可以看到此时的 LookupPath 为 /hello ,自然也能找到对应的handler

image-20260105170151372

image-20260105170501722

而 当 我 们 没 有 配 置 上 下 文 直 接 访 问 /static/. . /hello , 此 时 LookupPath 竟 然 是/static/ .. /hello ,这当然会找不到 handler 导致 handlerMethod 为 null ,所以我们的请求结果变成了404

image-20260105161104619

image-20260105160903193

而 当 我 们 没 有 配 置 上 下 文 直 接 访 问 /static/ . /hello , 此 时 contextPath 为 空 ,pathWithinApplication 的 值 为 /static/../hello , 所 以 返 回 的 lookupPath 为/static/. . /hello ,我们没有匹配到任何一个 handler

image-20260105161445603

看来问题出在这个 contextPath 上,这个值是通过 getContextPath 获取的

image-20260105163408249

继续打个断点分析,当我们没有配置上下文或者将上下文配置为 / 时, lastSlash 的值会为0,tomcat 会认为当前的服务中不存在上下文,直接返回 contextPath 为空

image-20260105164057498

而当我们配置了上下文, lastSlash 的值就不为0了,会继续走下面的处理逻辑

image-20260105164505217

首先会获取我们配置的上下文 /demo 以及我们具体访问的 /static/ . /demo/hello,接着往下走,最后它会将选出来的备选 candidate 和实际的上下文 canonicalContextPath 进行比较,显然,现在选出来的 candidate 是 /static ,和 /demo 肯定不一样,所以 match 结果为 false

image-20260105164924210

这是一个迭代的过程,当 /static 失败后,它会再找下一个 / 的位置,此时的 candidate 是/static/..

这一轮的迭代再次失败后,它会选择 /static/../demo

经 过 normalize , /static/../demo 变 成 /demo , 于 是 匹 配 到 实 际 的 上 下 文canonicalContextPath , match 值为 true ,返回上下文 /static/ . /demo

image-20260105165115343

原来不是只有配置了上下文之后 Spring 才能解析 . / ,这个解析 . / 的操作其实是发生在 spring- boot- starter- parent 内嵌的 Servlet 容器 Tomcat解析 context- path 里的!