前言

做ctftimes里面的题,又是java,狠心入门一下spring先

简单介绍

Spring是一个“一站式”框架,即Spring在JavaEE的三层架构[表现层(Web层)、业务逻辑层(Service层)、数据访问层(DAO层)]中,每一层均提供了不同的解决技术。如下:

  • 表现层(Web层 Controller):Spring MVC
  • 业务逻辑层(Service层 Service):Spring的IoC
  • 数据访问层(DAO层 Repository):Spring的jdbcTemplate

0x01

入门可参考:
https://blog.csdn.net/qq_15096707/article/details/72819930
https://blog.csdn.net/gavin_john/article/details/79517418

通过IDEA开发一个Spring的入门项目SpringFirst。
image.png
image.png
接下来的步骤参考:https://blog.csdn.net/gavin_john/article/details/79517418

重点:

  • 使用Spring,没有new对象,而是把创建对象的任务交给Spring框架
  • Spring怎么维护对象与对象之间的关系呢? –> 利用ref属性。
    看完上面两篇文章你就会对注入就会有一定的了解,给个例子。
package com.wm103.ioc;

import java.util.List;
import java.util.Map;
import java.util.Properties;

public class PropertyDemo {
    private String[] arrs;
    private List<String> list;
    private Map<String, String> map;
    private Properties properties;

    public String[] getArrs() {
        return arrs;
    }

    public void setArrs(String[] arrs) {
        this.arrs = arrs;
    }

    public List<String> getList() {
        return list;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }

    public Properties getProperties() {
        return properties;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="prop" class="com.wm103.ioc.PropertyDemo">
        <!-- 注入数组 -->
        <property name="arrs">
            <list>
                <value>Value 1 of Array</value>
                <value>Value 2 of Array</value>
                <value>Value 3 of Array</value>
            </list>
        </property>
        <!-- 注入List集合 -->
        <property name="list">
            <list>
                <value>Value 1 of List</value>
                <value>Value 2 of List</value>
                <value>Value 3 of List</value>
            </list>
        </property>
        <!-- 注入Map集合 -->
        <property name="map">
            <map>
                <entry key="key1" value="Value 1 of Map"></entry>
                <entry key="key2" value="Value 2 of Map"></entry>
                <entry key="key3" value="Value 3 of Map"></entry>
            </map>
        </property>
        <!-- 注入Properties -->
        <property name="properties">
            <props>
                <prop key="username">root</prop>
                <prop key="password">123456</prop>
            </props>
        </property>
    </bean>
</beans>
public void runPropertyDemo() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    PropertyDemo pd = (PropertyDemo) context.getBean("prop");
    System.out.println(pd);
    System.out.println(Arrays.toString(pd.getArrs()));
    System.out.println(pd.getList());
    System.out.println(pd.getMap());
    System.out.println(pd.getProperties());
}

从上面可以看到,在xml里面"赋值未免太复杂了",下面就介绍简便的方法。
注:配置文件和注解混合使用
1)创建对象的操作一般使用配置文件方式实现;
2)注入属性的操作一般使用注解方式实现。(不推荐@Component,那么何时用呢?当组件不好归类的时候,我们可以使用这个注解进行标注。)
注入属性参考:
使用Spring注解来注入属性
Spring中的注解使用总结

简单理解就是初始化构造。(java里面专业的叫配置元数据)
image.png

快捷方式如下:
最简单的一种方式就是加入下面的一句:
<context:component-scan base-package="com.xxx" />
它的意思就是开启自动扫描,会自动扫描你设置的包路径下的所有类,如果有注解就进行解析
这就是所谓的用注解来构造 IoC 容器;base-package 是可以指定多个包的,用逗号分割
image.png

image.png

常用注解

先上详文:史上最全的java spring注解,没有之一
你清楚这几个Spring常用注解吗?

常用的xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans
    xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-3.0.xsd
    ">
    
    <context:component-scan base-package="com.spring" />
    
    <bean id="zoo" class="com.spring.model.Zoo" />
    <bean id="tiger" class="com.spring.model.Tiger" />
    <bean id="monkey" class="com.spring.model.Monkey" />

</beans>

@Autowired

@Autowired 默认按类型匹配的方式,在容器查找匹配的 Bean,当有且仅有一个匹配的 Bean 时,Spring 将其注入 @Autowired 标注的变量中。

public class Zoo {
    @Autowired
    private Tiger tiger;
    
    @Autowired
    private Monkey monkey;
    
    public String toString(){
        return tiger + "\n" + monkey;
    }
}

@Qualifier

这个注解是用来指定注入 Bean 的名称 ;也就是说如果容器中有一个以上匹配的 Bean,则可以通过 @Qualifier 注解限定 Bean 的名称,否则调用的时候会抛异常
比如:某个 bean 中引用了一个接口,实现这个接口的 bean 有多个,Spring 在注入的时候就不知道注入那一个了,这样要么删除其他的 bean 要么就是使用 @Qualifier 注解

@Autowired
@Qualifier("bmwCar")
private ICar car;

@Resource

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入,而@Resource默认按byName自动注入罢了。

public class Zoo1 {
    
    @Resource(name="tiger")
    private Tiger tiger;
    
    @Resource(type=Monkey.class)
    private Monkey monkey;
    
    public String toString(){
        return tiger + "\n" + monkey;
    }
}

@Service

@Service
public class Zoo {
    @Autowired
    private Tiger tiger;
    
    @Autowired
    private Monkey monkey;
    
    public String toString(){
        return tiger + "\n" + monkey;
    }
}

这样,Zoo.java 在 Spring 容器中存在的形式就是 “zoo”,即可以通过 ApplicationContext.getBean("zoo") 来得到 Zoo 对象
@Service 注解,其实做了两件事情,这样上面的代码可以省去下面三行定义bean
1.声明 Zoo.java 是一个 bean,这点很重要,因为 Zoo.java 是一个 bean,其他的类才可以使用 @Autowired 将 Zoo 作为一个成员变量自动注入。
2.Zoo.java 在 bean 中的 id 是 “zoo”,即类名且首字母小写。

@Controller

@Controller 对应表现层的 Bean,也就是 Action,用法上倒是和 @Service 并没有什么区别

@Controller
@Scope("prototype")
public class UserAction {...}

@Repository

@Repository 对应数据访问层 Bean ;和上面类似,本质也是把类交给 Spring 管理而已,只不过对应的不同

@Repository(value="userDao")
public class UserDaoImpl extends BaseDaoImpl<User> {
………
}

@Configuration

该类等价 与XML中配置beans,相当于Ioc容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean,与xml中配置的bean意思一样。

@Configuration注解的类必需使用<context:component-scanbase-package="XXX"/>扫描.如下:

@Configuration
public class MainConfig {
 
//在properties文件里配置
    @Value("${wx_appid}")
public String appid;
  
     protected MainConfig(){}
 
    @Bean
    public WxMpService wxMpService() {
        WxMpService wxMpService = new WxMpServiceImpl();
        wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
        return wxMpService;
}
}

定义一个MainConfig,用@Configuration注解,那MainConfig相当于xml里的beans,里面用@Bean注解的和xml里定义的bean等价,用<context:component-scanbase-package=”XXX”/>扫描该类,最终我们可以在程序里用@AutoWired或@Resource注解取得用@Bean注解的bean,和用xml先配置bean然后在程序里自动注入一样。目的是减少xml里配置。

@value

参考:Spring注入值(Value注解)

@PostConstruct 和 @PreDestory

public class TestService { 
 
    @PostConstruct  
    public void  init(){  
        System.out.println("初始化");  
    }  
      
    @PreDestroy  
    public void  dostory(){  
        System.out.println("销毁");  
    }  
}

@PostConstruct:在构造方法和init方法(如果有的话)之间得到调用,且只会执行一次。
@PreDestory:注解的方法在destory()方法调用后得到执行。

image.png

引深一点,Spring 容器中的 Bean 是有生命周期的,Spring 允许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操作,常用的设定方式有以下三种:
1.通过实现 InitializingBean/DisposableBean 接口来定制初始化之后/销毁之前的操作方法;
2.通过 <bean> 元素的 init-method/destroy-method属性指定初始化之后 /销毁之前调用的操作方法;
3.在指定方法上加上@PostConstruct 或@PreDestroy注解来制定该方法是在初始化之后还是销毁之前调用
但他们之前并不等价。即使3个方法都用上了,也有先后顺序.
Constructor > @PostConstruct >InitializingBean > init-method

@Async

基于@Async标注的方法,称之为异步方法,这个注解用于标注某个方法或某个类里面的所有方法都是需要异步处理的。被注解的方法被调用的时候,会在新线程中执行,而调用它的方法会在原来的线程中执行。

application.xml形势的配置:

第一步配置XML。

<!--扫描注解,其中包括@Async -->
<context:component-scan base-package="com.test"/>
<!-- 支持异步方法执行, 指定一个缺省的executor给@Async使用-->
<task:annotation-driven executor="defaultAsyncExecutor"  /> 
<!—配置一个线程执行器-->
<task:executor id=" defaultAsyncExecutor "pool-size="100-10000" queue-capacity="10" keep-alive =”5”/>

参数解读:
<task:executor />配置参数:

  • id:当配置多个executor时,被@Async("id")指定使用;也被作为线程名的前缀。
  • pool-size:
  • core size:最小的线程数,缺省:1
  • max size:最大的线程数,缺省:Integer.MAX_VALUE
  • queue-capacity:当最小的线程数已经被占用满后,新的任务会被放进queue里面,当这个queue的capacity也被占满之后,pool里面会创* 建新线程处理这个任务,直到总线程数达到了max size,这时系统会拒绝这个任务并抛出TaskRejectedException异常(缺省配置的情况下,可以通过rejection-policy来决定如何处理这种情况)。缺省值为:Integer.MAX_VALUE
  • keep-alive:超过core size的那些线程,任务完成后,再经过这个时长(秒)会被结束掉
  • rejection-policy:当pool已经达到max size的时候,如何处理新任务
  • ABORT(缺省):抛出TaskRejectedException异常,然后不执行DISCARD:不执行,也不抛出异常
  • DISCARD_OLDEST:丢弃queue中最旧的那个任务
  • CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
    第二步在类或方法上添加@Async,当调用该方法时,则该方法即是用异常执行的方法单独开个新线程执行。
@Async(“可以指定执行器id,也可以不指定”)
    public static void testAsyncVoid (){
        try {
            //让程序暂停100秒,相当于执行一个很耗时的任务
    System.out.println(“异常执行打印字符串”);
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

当在外部调用testAsync方法时即在新线程中执行,由上面<task: annotation-driven/>执行器去维护线程。
总结:先用context:component-scan去扫描注解,让spring能识别到@Async注解,然后task:annotation-driven去驱动@Async注解,并可以指定默认的线程执行器executor。那么当用@Async注解的方法或类得到调用时,线程执行器会创建新的线程去执行。

@RequestMapping

处理映射请求的注解。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。有6个属性.
1、value ===>value = "/testValid"
2、method ===>method = RequestMethod.POST
3、consumes ===>consumes="application/json"
4、produces ===>指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
5、params ===>params="name=chenyuan"
6、headers ===>headers="Referer=www.baidu.com"

@ModelAttribute

参考:SpringMVC注解 @ModelAttribute
看了就秒懂了。

一个不太出名的springmvc漏洞------自动绑定漏洞

VolgaCTF 2019 Qualifier
Spring MVC 自动绑定漏洞
Spring MVC Autobinding漏洞实例初窥
Writeup for Shop in VolgaCTF 2019 Qualifier
image.png

image.png

image.png

具体分析可参照Twings师傅的文章。自动绑定漏洞+war远程调试

大写绕过的漏洞原因出现在这里:
https://github.com/spring-projects/spring-framework/blob/master/spring-beans/src/main/java/org/springframework/beans/CachedIntrospectionResults.java
image.png
这里当出现找不到属性的时候,会变成小写再去找一遍,最后绑定成功。