spring框架
第一个例子:首先设置一个接口Perofrmance表示参赛者。
package com.moonlit.myspring;public interface Performer { void perform() throws PerformanceException;}
创建一个Juggler(杂技师)类继承Performer表示参赛者是杂技师。
package com.moonlit.myspring;public class Juggler implements Performer { private int beanBags=3; public Juggler() { } public Juggler(int beanBags) { this.beanBags=beanBags; } public void perform() throws PerformanceException { System.out.println("JUGGLING " + beanBags + " BEANBAGS"); }}
在spring-idol.xml配置文件中定义一个名为duke的bean,他对应Juggler类(把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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="duke" class="e430-4f38-ebb4-d7ff com.moonlit.myspring.Juggler" /> </beans>
测试代码:
package com.moonlit.practice;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.moonlit.myspring.PerformanceException;import com.moonlit.myspring.Performer;public class FirstBean { public static void main(String[] args) throws PerformanceException { ApplicationContext context=new ClassPathXmlApplicationContext("spring-idol.xml"); Performer performer=(Performer) context.getBean("duke"); performer.perform(); }}
运行结果如下:
JUGGLING 3 BEANBAGS
理解:首先定义了一个接口Performer,然后写了一个类Juggler继承自Peformer,Juggler有一个私有成员变量beanBags,他的默认值是3,然后Juggler实现了Performer的perform方法,方法的输出带有beanBags的数量。然后在测试的程序中,通过ApplicationContext类型对象加载了spring-idol.xml文件的内容,而在xml文件中定义了名为"duke"的bean,然后刚好就用到了。然后bean返回的是一个Juggler,所以将
Performer performer=(Performer) context.getBean("duke");
改成
Juggler performer=(Juggler) context.getBean("duke");
也是可以的,但是在这里想看的效果是通过application context返回的是不是一个Juggler,因为通过输出的结果就可以知道了,所以这里用(Performer),对中输出的效果显示bean对应的Performer真的是一个Juggler,这就是通过xml定义一个bean并通过application context获得这个bean对象的整个过程。
构造器注入之前讲到的名为"duke"的bean有一个私有成员变量beanBags代表这个杂技师bean的一次性能够抛出的最多的数量,Juggler有一个构造函数,构造函数的第一个参数(这里只有一个参数)beanBags是一个整型的值,用于传递给Juggler的私有成员变量beanBags。构造器注入的方法是:在bean中添加一个constructor-arg(如果构造函数的参数有两个,那就添加两个constructor-arg)。在spring-idol.xml中修改bean "duke"如下:
<bean id="duke" class="4f38-ebb4-d7ff-c257 com.moonlit.myspring.Juggler" > <constructor-arg name="beanBags" value="15" /> </bean>
再次运行FirstBean程序,输出如下:
JUGGLING 15 BEANBAGS
可以看到通过构造器诸如已经把duke的beanBags改为了15。构造函数中的参数可能不是一个基础类型的变量,而可能是一个变量,这个时候只要把constructor-arg的value改成ref即可,ref对应的值需要被声明称一个bean元素。使用一个会唱歌的杂技师PoeticJuggler类来演示,PoeticJuggler继承自Juggler,它具有一个Poem类型的私有成员变量poem,代表他要朗诵的诗歌。Poem类:
package com.moonlit.myspring;public interface Poem { void recite();}
定义一首名为Sonnet29的类用于表示名为一首sonnet29的诗:http://shakespeare-online.com/sonnets/29.htmlSonnet29实现了Poem接口。
package com.moonlit.myspring;public class Sonnet29 implements Poem { private static String[] LINES={ "When, in disgrace with fortune and men's eyes,", "I all alone beweep my outcast state,", "And trouble deaf heaven with my bootless cries,", "And look upon myself, and curse my fate,", "Wishing me like to one more rich in hope,", "Featur'd like him, like him with friends possess'd,", "Desiring this man's art and that man's scope,", "With what I most enjoy contented least;", "Yet in these thoughts myself almost despising,", "Haply I think on thee, and then my state,", "Like to the lark at break of day arising", "From sullen earth, sings hymns at heaven's gate;", "For thy sweet love remember'd such wealth brings", "That then I scorn to change my state with kings.", }; public Sonnet29() { } public void recite() { for (String line : LINES) System.out.println(line); }}
有了Poem和他的一个实现类Sonnet29之后,开始来写PoeticJuggler,他继承自Juggler并且有一个Poem类型私有成员变量poem。
package com.moonlit.myspring;public class PoeticJuggler extends Juggler { private Poem poem; public PoeticJuggler(Poem poem) { super(); this.poem=poem; } public PoeticJuggler(int beanBags, Poem poem) { super(beanBags); this.poem=poem; } public void perform() throws PerformanceException { super.perform(); System.out.println("While reciting..."); poem.recite(); }}
并且,需要在xml文件中声明Sonnet29和PoeticJuggler类对应的bean。
<bean id="sonnet29" class="ebb4-d7ff-c257-cc2c com.moonlit.myspring.Sonnet29" /> <bean id="poeticDuke" class="d7ff-c257-cc2c-7d95 com.moonlit.myspring.PoeticJuggler"> <constructor-arg value="16" /> <constructor-arg ref="sonnet29" /> </bean>
可以看到,"poeticDuke"使用了两个constructor-arg来声明参数,第一个参数使用value,第二个参数使用ref,Sonnet29类型的bean--"connet29"。使用测试程序查看效果:
package com.moonlit.practice;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.moonlit.myspring.PerformanceException;import com.moonlit.myspring.Performer;public class FirstBean { public static void main(String[] args) throws PerformanceException { ApplicationContext context=new ClassPathXmlApplicationContext( "spring-idol.xml"); Performer performer=(Performer) context.getBean("duke"); performer.perform(); }}
程序输出如下:
JUGGLING 16 BEANBAGSWhile reciting...When, in disgrace with fortune and men's eyes,I all alone beweep my outcast state,And trouble deaf heaven with my bootless cries,And look upon myself, and curse my fate,Wishing me like to one more rich in hope,Featur'd like him, like him with friends possess'd,Desiring this man's art and that man's scope,With what I most enjoy contented least;Yet in these thoughts myself almost despising,Haply I think on thee, and then my state,Like to the lark at break of day arisingFrom sullen earth, sings hymns at heaven's gate;For thy sweet love remember'd such wealth bringsThat then I scorn to change my state with kings.
理解:可以通过构造器注入来模拟构造函数传入的参数,通过constructor-arg value="XX"传递一个基本类型的参数XX,通过constructor-arg ref="XX"传递一个bean。
注入各种bean属性这里通过一个MoonlightPoet类来演示了注入Bean属性property的效果。
package com.moonlit.myspring;import java.util.List;import java.util.Map;import java.util.Map.Entry;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import java.util.Properties;public class MoonlightPoet { private String name; private int age; private Poem poem; private List<String> list; private Map<String, String> map; public void perform() { System.out.println("name : " + name); System.out.println("age : " + age); poem.recite(); for (String val : list) System.out.println("in list : " + val); for (Entry<String, String> entry : map.entrySet()) System.out.println("in map : " + entry.getKey() + " -- " + entry.getValue()); } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext( "spring-idol.xml"); MoonlightPoet moonlightPoet=(MoonlightPoet) context.getBean("moonlightPoet"); moonlightPoet.perform(); } public String getName() { return name; } public void setName(String name) { this.name=name; } public int getAge() { return age; } public void setAge(int age) { this.age=age; } public Poem getPoem() { return poem; } public void setPoem(Poem poem) { this.poem=poem; } 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; }}
该bean在xml文件中定义如下:
<bean id="moonlightPoet" class="c257-cc2c-7d95-2177 com.moonlit.myspring.MoonlightPoet"> <property name="name" value="moonlit" /> <property name="age" value="22" /> <property name="poem" ref="sonnet29" /> <property name="list"> <list> <value>hello</value> <value>world</value> <!-- if bean, use <ref bean="XX"> --> </list> </property> <property name="map"> <map> <entry key="key1" value="value1" /> <entry key="key2" value="value2" /> <entry key="key3" value="value3" /> </map> </property></bean>
输出结果:
name : moonlitage : 22When, in disgrace with fortune and men's eyes,I all alone beweep my outcast state,And trouble deaf heaven with my bootless cries,And look upon myself, and curse my fate,Wishing me like to one more rich in hope,Featur'd like him, like him with friends possess'd,Desiring this man's art and that man's scope,With what I most enjoy contented least;Yet in these thoughts myself almost despising,Haply I think on thee, and then my state,Like to the lark at break of day arisingFrom sullen earth, sings hymns at heaven's gate;For thy sweet love remember'd such wealth bringsThat then I scorn to change my state with kings.in list : helloin list : worldin map : key1 -- value1in map : key2 -- value2in map : key3 -- value3
理解:注入简单值:<property name="XX" value="YY" />其中XX是变量名,YY是值。引用其他Bean:<property name="XX" ref="YY">其中XX是变量名,YY是引用的bean的id。
装配List:<property name="XX"><value>YY</value>或者<ref bean="ZZ"></property>其中XX是变量名,YY是值,ZZ是引用的bean。装配Map:<map><entry key="XX" value="YY" />或者<entry key="XX" value-ref="YY" />或者<entry key-ref="XX" value="YY" />或者<entry key-ref="XX" value-ref="YY" /></map>因为map的key和value可以对应一个基础类型的值,也可以对应一个bean,所以key,value对应值,key-ref,value-ref对应bean。
使用Spring的命名空间p装配属性可以在beans中添加
xmlns:p="http:www.springframework.org/schema/beans"
来使用p:作为<bean>元素所有属性的前缀来装配Bean的属性。用法如下:
<bean id="kenny" class="cc2c-7d95-2177-0597 XX" p:song="Jingle Bells" p:instrument-ref="saxphone" />
-ref后缀作为一个标识来告知Spring应该装配一个引用而不是字面值。
自动装配bean属性Spring提供了四种类型的自动装配策略:
byName – 把与Bean的属性具有相同名字(或者ID)的其他Bean自动装配到Bean的对应属性中。
byType – 把与Bean的属性具有相同类型的其他Bean自动装配到Bean的对应属性中。
constructor – 把与Bean的构造器入参具有相同类型的其他Bean自动装配到Bean的对应属性中。
autodetect – 首先使用costructor进行自动装配。如果失败,再尝试使用byType进行自动装配。这里以关羽和青龙偃月刀为例: 首先定义一个武器接口Weapon:
package com.moonlit.myspring;public interface Weapon { public void attack();}
然后定义一个Weapon接口的实现Falchion类:
package com.moonlit.myspring;public class Falchion implements Weapon { public void attack() { System.out.println("falcon is attacking!"); }}
定义一个英雄接口Hero:
package com.moonlit.myspring;public interface Hero { public void perform();}
然后定义一个Hero接口的实现Guanyu类(代表关羽):
package com.moonlit.myspring;public class GuanYu implements Hero { private Weapon weapon; public void perform() { System.out.println("GuanYu pick up his weapon."); weapon.attack(); } public Weapon getWeapon() { return weapon; } public void setWeapon(Weapon weapon) { this.weapon=weapon; }}
在不涉及自动装配的情况下,想要通过Spring的DI将Fachion类对象注入到Guanyu类的weapon属性中,可以新建一个xml文件(这里取名为spring-idol.xml)并在里面填写:spring-idol.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean id="falchion" class="7d95-2177-0597-1526 com.moonlit.myspring.Falchion" /> <bean id="guanyu" class="2177-0597-1526-ca31 com.moonlit.myspring.GuanYu"> <property name="weapon" ref="falchion" /> </bean> </beans>
其中最主要的内容就是两个bean的声明部分:
<bean id="falchion" class="46fb-9ee1-0500-3bc2 com.moonlit.myspring.Falchion" /> <bean id="guanyu" class="9ee1-0500-3bc2-b646 com.moonlit.myspring.GuanYu"> <property name="weapon" ref="falchion" /> </bean>
第一个bean标签定义了一个Falchion类型的bean,第二个bean标签中将第一个bean作为weapon的值装配到了weapon属性中。 然后可以写一个测试程序来查看效果:
package com.moonlit.practice;import org.springframework.context.ApplicationContext;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.moonlit.myspring.Hero;public class AutowirePractice { public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring-idol.xml"); Hero guanyu=(Hero) context.getBean("guanyu"); guanyu.perform(); }}
输出结果如下:
GuanYu pick up his weapon.falcon is attacking!
到目前为止还没有涉及到自动装配的内容,接下来开始讲述自动装配的内容。
byName自动装配改变spring-idol.xml中bean声明内容的形式如下:
<bean id="weapon" class="0500-3bc2-b646-b20c com.moonlit.myspring.Falchion" /> <bean id="guanyu" class="3bc2-b646-b20c-4146 com.moonlit.myspring.GuanYu" autowire="byName" />
得到一样的结果。
将Falchion类的id去了一个和Guanyu类的属性weapon一样的名字,并且在guanyu bean中添加了autowire="byName"用于指明装配类型是byName自动装配。这个时候guanyu bean就是在上下文中找名为weapon的bean装配到他自己的weapon属性中。
byType自动装配改变spring-idol.xml中bean声明内容的形式如下:
<bean id="falchion" class="b646-b20c-4146-e430 com.moonlit.myspring.Falchion" /> <bean id="guanyu" class="b20c-4146-e430-4f38 com.moonlit.myspring.GuanYu" autowire="byType" />
得到一样的结果。
这里已经不用关注Falchion类对应的bean的id是什么了,因为已经定义guanyu bean的autowire属性为"byType"。这个时候guanyu bean会在上下文中寻找和weapon具有相同类型的类对应的bean。因为Guanyu类的weapon实现Weapon借口,整个上下文中目前只有一个Weapon接口的实现Falchion类,所以以"byType"类型就检测到了falchion bean并将其注入到了guanyu bean的weapon属性中。但是也会出现一种情况就是检测的时候可能会出现多个相同type的bean,这个时候就不知道要装配那个了。比如,在新建一个实现Weapon接口的方天画戟类HalBerd:
package com.moonlit.myspring;public class Halberd implements Weapon { public void attack() { System.out.println("halberd is attacking!!!"); }}
并且在xml文件中声明一个新的halberd bean:
<bean id="halberd" class="4146-e430-4f38-ebb4 com.moonlit.myspring.Halberd" />
在这种情况下就会出错,因为有两个bean满足byType的结果。
这个时候有两种解决办法:第一种方法是将其中一个bean的primary属性设为false,比如:将方天画戟falchion bean的primary属性设为true,以防冠以使用方天画戟(很好奇吕布死了之后,赤兔马归关羽了,方天画戟去哪里了):
<bean id="falchion" class="e430-4f38-ebb4-d7ff com.moonlit.myspring.Falchion" /> <bean id="halberd" class="4f38-ebb4-d7ff-c257 com.moonlit.myspring.Halberd" primary="true" /> <bean id="guanyu" class="ebb4-d7ff-c257-cc2c com.moonlit.myspring.GuanYu" autowire="byType" />
输出结果如下:
GuanYu pick up his weapon.halberd is attacking!!!
从输出结果中可以看到,关羽没有使用青龙偃月刀,而是使用方天画戟进行攻击了。
primary的默认属性是false。第二种方法是设置其中一个bean的autowire-candidate属性为false,比如:将方天画戟的autowire-candidate属性设为false:
<bean id="falchion" class="d7ff-c257-cc2c-7d95 com.moonlit.myspring.Falchion" /> <bean id="halberd" class="c257-cc2c-7d95-2177 com.moonlit.myspring.Halberd" primary="true" autowire-candidate="false" /> <bean id="guanyu" class="cc2c-7d95-2177-0597 com.moonlit.myspring.GuanYu" autowire="byType" />
这个时候测试程序的输出如下:
GuanYu pick up his weapon.falcon is attacking!
可以看到这个时候关羽又重拾了青龙偃月刀。可以看到,当halberd bean的autowire-candidate属性设为false时,他将不会作为自动装配的竞选bean之一,这个时候虽然halberd的primary属性为true,但是halberd bean没有参与自动装配的竞选,所以自动装配到了falchion。
这种感觉就好像:“隔壁村李小花觊觎已久,但是一个要成为海贼王的男人,于是拒绝了她……最终她嫁给了隔壁老王,过上了幸福的生活”。
使用注解装配bean使用@Autowired注解
从Spring2.5开始,最有趣的一种装配Spring Bean的方式是使用注解自动装配Bean的属性。Spring默认禁用注解装配,最简单的启用方式是使用Spring的context命名空间配置中的<context:annotation-config>元素,如下所示:
<?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: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:annotation-config /> <!-- bean declarations here --> </beans>
继续上一节的例子,在xml文件中定义两个bean:falchion bean和guanyu bean,为了实现@Autowired自动装配,在GuanYu类中的setWeapon()方法前添加了@Autowired注解,如下:GuanYu.java:
package com.moonlit.myspring;import org.springframework.beans.factory.annotation.Autowired;public class GuanYu implements Hero { private Weapon weapon; public void perform() { System.out.println("Guan Yu pick up his weapon."); weapon.attack(); } public Weapon getWeapon() { return weapon; } @Autowired public void setWeapon(Weapon weapon) { this.weapon=weapon; }}
通过基于注解的方式,可以不用在xml文件中为guanyu bean添加autowire属性了。spring-idol内部的代码:
<context:annotation-config /> <bean id="falchion" class="7d95-2177-0597-1526 com.moonlit.myspring.Falchion" /> <bean id="guanyu" class="2177-0597-1526-ca31 com.moonlit.myspring.GuanYu" />
不仅可以使用@Autowired注解标注setter方法,还可以标注需要自动装配bean引用的任一方法,比如,给GuanYu类的setWeapon方法改名为pickupWeapon,如下:
package com.moonlit.myspring;import org.springframework.beans.factory.annotation.Autowired;public class GuanYu implements Hero { private Weapon weapon; public void perform() { System.out.println("Guan Yu pick up his weapon."); weapon.attack(); } public Weapon getWeapon() { return weapon; } @Autowired public void pickupWeapon(Weapon weapon) { this.weapon=weapon; }}
再运行测试程序AutowirePractice,输出的结果是一样的,因为虽然没有了setWeapon方法,但是通过@Autowired注解通过pickupWeapon方法将falchion bean传递给了guanyu bean。@Autowired注解甚至可以标注构造器,这样的话甚至连set方法都可以不写了:
package com.moonlit.myspring;import org.springframework.beans.factory.annotation.Autowired;public class GuanYu implements Hero { @Autowired private Weapon weapon; public void perform() { System.out.println("Guan Yu pick up his weapon."); weapon.attack(); }}
@Autowired注解存在两种限制:1.没有匹配Bean2.匹配多个Bean
不过都有解决办法。可选的自动装配默认情况下,@Autowired属性具有强契约特征,其所标注的属性或参数必须是可装配的。如果没有Bean可以装配到@Autowired所标注的属性或参数中,自动装配就会失败(抛出令人讨厌的NoSuchBeanDefinitionException)。属性不一定非要装配,null值也是可以接受的。在这种场景下,可以通过设置@Autowired的required属性为false来配置自动装配式可选的。例如:
@Autowired(required=false)private Weapon weapon;
限定歧义性的依赖有可能存在多个bean满足装配条件,比如,这里,falchion bean和halberd bean都满足装配到guanyu bean的weapon属性中的条件。此时如果只是用@Autowired注解的话就会出问题,才@Autowired注解下添加@Qualifier注解如下:
@Autowired @Qualifier("falchion") private Weapon weapon;
就会将falchion bean装入到weapon中。如上所示,@Qualifier注解将尝试注入ID为falchion的Bean。除了通过Bean的ID来限定,也可以给Bean添加一个qualifier属性,通过这个qualifier属性来获得限定,如:给halberd bean添加一个qualifier,值为"weaponOfGuanYu":
<bean id="halberd" class="46fb-9ee1-0500-3bc2 com.moonlit.myspring.Halberd"> <qualifier value="weaponOfGuanYu" /> </bean>
然后对GuanYu类weapon类的注解如下:
@Autowired @Qualifier("weaponOfGuanYu") private Weapon weapon;
输出如下:
Guan Yu pick up his weapon.halberd is attacking!!!
可以看出,@qualifier降低了@Autowired的匹配范围,最终筛选得到了halberd bean装入weapon属性。这里的<qualifier>元素限定了方天画戟(halberd)Bean是关羽使用的武器(weaponOgGuanYu)。除了可以在XML中指定qualifier,还可以使用Qualifier类来标注Halberd类:
package com.moonlit.myspring;import org.springframework.beans.factory.annotation.Qualifier;@Qualifier("weaponOfGuanYu")public class Halberd implements Weapon { public void attack() { System.out.println("halberd is attacking!!!"); }}
程序运行将得到相同的结果。即使<context:annotation-config>有助于完全消除Spring配置文件中的元素,但是还是不能完全消除,仍然需要使用<bean>元素显示定义Bean。因此<context:component-scan>元素出现了,它除了完成<context:annotation-config>一样的工作,还允许Spring自动检测Bean和定义Bean。这就意味着不使用<bean>元素,Spring应用中的大多数(或者所有)Bean都能够自动实现定义和装配。
自动检测为了配置Spring自动检测,需要使用<context:component-scan>元素来代替<context:annotation-config>元素:
<?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.moonlit.myspring"> </context:component-scan></beans>
<context:component-scan>元素会扫描指定的包以及所有子包,并查找出能够自动注册为Spring Bean的类。base-package属性标示了<context:component-scan>元素所扫描的包。
为自动检测标注Bean
默认情况下,<context:component-scan>查找使用构造型(stereotype)注解所标注的类,这些特殊的注解如下:
类型 说明@component 通用的构造型注解,标示该类为Spring 组件。@Controller 标识将该类定义为Spring MVC controller。@Repository 标识将该类定义为数据仓库(例如:Dao层)。@Service 标识将该类定义为服务(例如:Service层)。
@component("guanyu")
整理 | 苏宓
出品 | CSDN(ID:CSDNnews)
继 Log4j 2 之后,听闻 Java 再次遭到漏洞攻击,这一次,似乎情况也更为严重,因为受到影响的是 Java 平台的开源全栈应用程序框架和控制反转容器实现——Spring 家族,而且网传漏洞还不止一个。
一直以来,Spring 是编程开发的必选技术之一,此前一位名为 Bogdan N. 的全栈开发者甚至评价道:“学习 Java、学习 Spring 框架,你永远都不会失业。”
可想而知,如果 Spring 城门失火,Java 必定遭殃。不过,Spring RCE 漏洞在网络上炒了两天,虽然有不少安全圈人员纷纷发圈,但更多的还是表示了只是听闻,这也不禁让人质疑,是真有漏洞,还是虚惊一场?
接下来,本文将盘点一下这两天 Spring 的“大”漏洞。
第一个:真的 Spring Cloud Function SPEL RCE
3 月 26 日,据网络安全网站 Cyber Kendra 报道,Spring Cloud Function 官方测试用例曝光了 Spring Cloud Function SPEL(Spring Expression Language)表达式注入漏洞,黑客可利用该漏洞注入 SPEL 表达式来触发远程命令执行。
漏洞发现过程
起初,研究人员在分析 Spring Cloud 函数的 main 分支(https://github.com/spring-cloud/spring-cloud-function/commit/dc5128b80c6c04232a081458f637c81a64fa9b52)时,发现有开发者向其中添加了 SimpleEvaluationContext 类。还使用了 isViaHeadervariable 作为标志,在解析 spring.cloud.function.routing-expression 之前判断的值取自 HTTP header。
spring.cloud.function.routing-expression 的参数存在于访问 Spring Cloud Function 的 HTTP 请求头中,其 SpEL 表达式可以通过 StandardEvaluationContext 注入并执行。这使得攻击者可以利用这个漏洞进行远程命令执行。
Spring Cloud Function 的应用
当前,Spring Cloud Function 被许多科技巨头应用于产品中,包括 AWS Lambda、Azure、Google Cloud Functions、Apache OpenWhisk 以及许多 Serverless 服务提供商。
根据官方文档,Spring Cloud Function 是基于 Spring Boot 的函数计算框架,它可以:
通过函数促进业务逻辑的实现。
将业务逻辑的开发生命周期与任何特定的运行时目标分离,以便使用相同的代码可以作为 Web 端点、流处理器或任务运行。
支持跨 Serverless 提供商的统一编程模型,具备独立运行(本地或在 PaaS 中)的能力。
在 Serverless 上提供程序上启用 Spring Boot 功能(自动配置、依赖注入、指标)。
简而言之,Spring Cloud Function 通过抽象传输细节和基础设施,为开发者保留熟悉的开发工具和开发流程,让开发者专注于实现业务逻辑,从而提高开发效率。
影响
目前,Spring Cloud Function SPEL 漏洞已被归类为严重等级,CVSS(通用安全漏洞评分系统) 得分为 9.0(满分 10)。
不过,开发者也无须太过担心,因为只有 Spring Cloud Function 的某些版本特定配置(3.0.0.RELEASE <=Spring Cloud Function <=3.2.2)的动态路由受到影响,受影响的版本发布时间在 2019 年 11 月 22 日至 2022 年 2 月 17 日。
此外,官方也针对漏洞发布修复补丁:https://github.com/spring-cloud/spring-cloud-function/commit/0e89ee27b2e76138c16bcba6f4bca906c4f3744f
以及发布了最新的 3.1.7 和 3.2.3 版本:https://github.com/spring-cloud/spring-cloud-function/tags
第二个:网传比 Spring Cloud Function RCE 更为严重的 Spring RCE
相比前者,3 月 29 日晚间,有不少网友爆料的 Spring RCE 漏洞,让开发者圈中人心惶惶。
不过有些不同寻常的是,这个漏洞目前并没有像 Log4j2 事件那样引起的圈内诸多企业大厂的紧急行动,也不像 Spring Cloud Function SPEL 漏洞那样有官方说明,甚至连国外披露漏洞的根源也是来自 QQ 和国内部分网络安全网站。
这也让不少网友猜测,该漏洞应该是国内某个安全机构、安全人员最先发现的。这不,有网友将该漏洞发布到了 GitHub 上(目前已删除,但有网友将该页面保存了下来):
来源:https://archive.ph/DIbrv
根据网传的内容显示:
Spring 框架出现的 RCE 0day 漏洞影响的范围为 JDK 版本号在 9 及以上的、使用了 Spring 框架或衍生框架。
目前,随着该网传漏洞的暗流涌动,国外不少网络安全研究人员和安全公司也发布 Twitter 表示这一漏洞的存在:
他们也将该漏洞称之为 Spring4Shell,猜测是由传递参数的不安全反序列化引起的。
Spring 零日漏洞真的存在?
针对网传的内容,全球领先的安全风险信息解决方案提供商 Rapid7 通过《Spring4Shell: Zero-Day Vulnerability in Spring Framework》一文也对外确认零日漏洞是真实存在的。
其在文章中表示,该漏洞似乎影响了使用 @RequestMapping 注解和 POJO(Plain Old Java Object)参数的函数。与此同时,Rapid7 还通过 Springframework MVC 进行了演示(https://www.rapid7.com/blog/post/2022/03/30/spring4shell-zero-day-vulnerability-in-spring-framework/):
这里有一个控制器 ( HelloWorldController),当它被加载到 Tomcat 中时,它将为 http://name/appname/rapid7 中处理 HTTP 请求。处理请求的函数被称之为 vulnerable,而且还有一个 POJO 参数 HelloWorld。在这里,HelloWorld 被简化了,但如果需要,POJO 可能会变得非常复杂:
基于此,Rapid7 表示,“如果我们编译该项目,并将其托管在 Tomcat 上,我们就可以用下面的 curl 命令来利用它。请注意,下面使用的有效载荷与研究人员创建的原始概念证明所使用的完全相同。”
这个有效载荷在 Tomcat ROOT 目录下投放了一个受密码保护的 webshell,名为 tomcatwar.jsp,它看起来像这样:
然后,攻击者可以调用命令。下面是一个执行 whoami 以获得 albinolobster 的例子:
通过测试,Rapid7 发现在 OpenJDK 1.8.0_312 上的测试失败了,但 OpenJDK 11.0.14.1 有作用。
临时修复方案
目前由于 Spring 官方尚未发布最新说明,无法确定哪些应用程序使用了有漏洞的功能,这也导致但凡和 Spring 沾点关系的功能,开发者都要问上一句「是否涉及 Spring Core 的 RCE 0day 漏洞」。
不过,据外媒 BleepingComputer 报道,这个漏洞虽然会影响到 JDK 版本号在 9 及以上的、使用了 Spring 框架或衍生框架,但是也有特定的前提条件。
同时,也有不少网友调侃道:
别慌,虽然现在 Java 已经到了 Java 18 版本,但是很多企业仍然停留在 Java 8 版本及以下。
不同的安全漏洞,也让 Java 工程师产生了严重的心理阴影:
对于开发者、企业而言,当前也不用自乱阵脚,不过,为了避免一定的风险,可以做的就是先排查一下自己的 JDK 版本以使用 Spring 框架或衍生框架 的情况,如果版本在 JDK 8 及以下,则不受漏洞的影响。
此外,根据 rapid 7、国内的北京大学计算中心等渠道给出的临时修复方案,大家也可以尝试一波:
在应用中全局搜索@InitBinder 注解,看看方法体内是否调用dataBinder.setDisallowedFields 方法,如果发现此代码片段的引入,则在原来的黑名单中,添加 {"class.*", "Class. *", "*. class.*", "*.Class.*"}。
注:如果此代码片段使用较多,需要每个地方都追加。
在应用系统的项目包下新建以下全局类,并保证这个类被Spring 加载(推荐在Controller 所在的包中添加)。完成类添加后需对项目进行重新编译打包和功能验证测试,并重新发布项目。
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;
@ControllerAdvice
@Order(10000)
public class GlobalControllerAdvice{
@InitBinder
public void setAllowedFields(webdataBinder dataBinder){
Stringabd=new string{"class.*","Class.*","*.class.*","*.Class.*"};
dataBinder.setDisallowedFields(abd);
}
}
最后,静待官方消息。
参考资料:
https://www.cyberkendra.com/2022/03/rce-0-day-exploit-found-in-spring-cloud.html
https://its.pku.edu.cn/announce/tz20220330110501.jsp
https://github.com/spring-projects/spring-framework/commit/7f7fb58dd0dae86d22268a4b59ac7c72a6c22529
https://www.bleepingcomputer.com/news/security/new-spring-java-framework-zero-day-allows-remote-code-execution/
https://www.rapid7.com/blog/post/2022/03/30/spring4shell-zero-day-vulnerability-in-spring-framework/
END 《新程序员001-004》全面上市,对话世界级大师,报道中国IT行业创新创造 成就一亿技术人
下一篇:在ppt中如何制作饼图
发表评论