@PreAuthorize,@PostAuthorize, @Secured注解+EL表达式
说明
(1)JDK版本:1.8
(2)Spring Boot 2.0.6
(3)Spring Security 5.0.9
(4)Spring Data JPA 2.0.11.RELEASE
(5)hibernate5.2.17.Final
(6)MySQLDriver 5.1.47
(7)MySQL 8.0.12
一、注解式方法级安全开启
需要在WebSecuirtyConfig添加配置:
@Configuration @EnableWebSecurity //启用Spring Security. //会拦截注解了@PreAuthrize注解的配置. @EnableGlobalMethodSecurity(prePostEnabled=true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter{ }
二、允许的注解
这里主要@PreAuthorize, @PostAuthorize, @Secured这三个注解可以使用。
2.1 @Secured
当@EnableGlobalMethodSecurity(securedEnabled=true)的时候,@Secured可以使用:
@GetMapping("/helloUser") @Secured({"ROLE_normal","ROLE_admin"}) public String helloUser() { return "hello,user"; }
说明:拥有normal或者admin角色的用户都可以方法helloUser()方法。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“。
如果我们要求,只有同时拥有admin & noremal的用户(上面是两者择一就行,这里两者都要满足)才能使用方法helloUser()方法,这时候@Secured就无能为力了。
2.2 @PreAuthorize
Spring的 @PreAuthorize/@PostAuthorize 注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制。
当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PreAuthorize可以使用:
@GetMapping("/helloUser") @PreAuthorize("hasAnyRole('normal','admin')") public String helloUser() { return "hello,user"; }
说明:拥有normal或者admin角色的用户都可以方法helloUser()方法。
此时如果我们要求用户必须同时拥有normal和admin的话,那么可以这么编码:
@GetMapping("/helloUser") @PreAuthorize("hasRole('normal') AND hasRole('admin')") public String helloUser() { return "hello,user"; }
2.3 @PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。
当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PostAuthorize可以使用:
@GetMapping("/helloUser") @PostAuthorize(" returnObject!=null && returnObject.username == authentication.name") public User helloUser() { Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); User user; if("anonymousUser".equals(pricipal)) { user = null; }else { user = (User) pricipal; } return user; }
这三个最常用也就是@PreAuthorize这个注解了,在使用中主要是配合Spring EL表达式。
三、EL表达式
什么是SpringEL?
Spring3中引入了Spring表达式语言—SpringEL,SpEL是一种强大,简洁的装配Bean的方式,他可以通过运行期间执行的表达式将值装配到我们的属性或构造函数当中
更可以调用JDK中提供的静态常量,获取外部Properties文件中的的配置
为什么要使用SpringEL?
平常通过配置文件或Annotaton注入的Bean,其实都可以称为静态性注入,试想一下,若然我Bean A中有变量A,它的值需要根据Bean B的B变量为参考,在这场景下静态注入就对这样的处理显得非常无力,
而Spring3增加的SpringEL就可以完全满足这种需求,而且还可以对不同Bean的字段进行计算再进行赋值,功能非常强大
如何使用SpringEL?
SpringEL从名字来看就能看出,和EL是有点关系的,SpringEL的使用和EL表达式的使用非常相似,EL表达式在JSP页面更方便的获取后台中的值,而SpringEL就是为了更方便获取Spring容器中的Bean的值,
EL使用${},而SpringEL使用#{}进行表达式的声明。
使用SpringEL注入简单值
public class TestSpringEL { /* * @Value注解等同于XML配置中的<property/>标签, * SpringEL同样支持在XML<property/>中编写 */ // 注入简单值,输出num为5 @Value("#{5}") private Integer num; // 注入ID为testConstant的Bean @Value("#{testConstant}") private TestConstant Constant; // 注入ID为testConstant Bean中的STR常量/变量 @Value("#{testConstant.STR}") private String str; }
使用SpringEL调用方法
public class TestSpringEL { /* * TestConstant类中有两个方法重载, * 返回值为String类型 */ // 调用无参方法 @Value("#{testConstant.showProperty}") private String method1; // 有参接收字符串的方法 @Value("#{testConstant.showProperty('Hello')}") private String method2; /* * 若然希望方法返回的String为大写 */ @Value("#{testConstant.showProperty().toUpperCase()}") private String method3; /* * 若使用method3这种方式,若然showProperty返回为null, * 将会抛出NullPointerException,可以使用以下方式避免 */ @Value("#{testConstant.showProperty()?.toUpperCase}") private String method4; /* * 使用?.符号代表若然左边的值为null,将不执行右边方法, * 读者可以灵活运用在其他场景,只要左边可能返回null, * 即可使用上面示例中的?. */ }
SpringEL调用静态类或常量
public class TestSpringEL { /* * 注入JDK中的工具类常量或调用工具类的方法 */ // 获取Math的PI常量 @Value("#{T(java.lang.Math).PI") private double pi; // 调用random方法获取返回值 @Value("#{T(java.lang.Math).random()}") private double ramdom; // 获取文件路径符号 @Value("#{T(java.io.File).separator}") private String separator; }
SpringEL运算
public class TestSpringEL { /* * 使用SpringEL进行运算及逻辑操作 */ // 拼接字符串 @Value("#{testConstant.nickname + ' ' + testConstant.name}") private String concatString; // 对数字类型进行运算,testConstant拥有num属性 @Value("#{ 3 * T(java.lang.Math).PI + testConstant.num}") private double operation; // 进行逻辑运算 @Value("#{testConstant.num > 100 and testConstant.num <= 200}") private boolean logicOperation; // 进行或非逻辑操作 @Value("#{ not testConstant.num == 100 or testConstant.num <= 200}") private boolean logicOperation2; // 使用三元运算符 @Value("#{testConstant.num > 100 ? testConstant.num : testConstant.num + 100}") private Integer logicOperation3; }
SpringEL使用正则表达式
public class TestSpringEL { // 验证是否邮箱地址正则表达式 @Value("#{testConstant.STR match '\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+'}") private boolean regularExpression; }
SpringEL操作集合
public class TestSpringEL { /* * TestConstant类中拥有名为testList的List变量, 和名为testMap的Map */ // 获取下标为0的元素 @Value("#{testConstant.testList[0]}") private String str; // 获取下标为0元素的大写形式 @Value("#{testConstant.testList[0]?.toUpperCase()}") private String upperStr; // 获取map中key为hello的value @Value("#{testConstant.testMap['hello']}") private String mapValue; // 根据testList下标为0元素作为key获取testMap的value @Value("#{testConstant.testMap[testConstant.testList[0]]}") private String mapStrByTestList; }
Spring操作外部Properties文件
<!-- 首先通过applicaContext.xml中<util:properties>增加properties文件 --> <!-- 注意需要引入Spring的util schemea命名空间和注意id属性,id属性将在SpringEL中使用 --> <util:properties id="test" location="classpath:application.properties"/>
public class TestSpringEL { // 注意test为xml文件中声明的id @Value("#{test['jdbc.url']}") private String propertiesValue; }
SpringEL查询筛选集合和投影
public class TestSpringEL { /* * 声明City类,有population属性 testContants拥有名叫cityList的City类List集合 */ // 过滤testConstant中cityList集合population属性大于1000的全部数据注入到本属性 @Value("#{testConstant.cityList.?[population > 1000]}") private List<City> cityList; // 过滤testConstant中cityList集合population属性等于1000的第一条数据注入到本属性 @Value("#{testConstant.cityList.^[population == 1000]}") private City city; // 过滤testConstant中cityList集合population属性小于1000的最后一条数据注入到本属性 @Value("#{testConstant.cityList.$[population < 1000]}") private City city2; /* * 首先为city增加name属性,代表城市的名称 */ /* * 假如我们在过滤城市集合后只想保留城市的名称, * 可以使用如下方式进行投影 */ @Value("#{testConstant.cityList.?[population > 1000].![name]}") private List<String> cityName; }