說明
(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; }