原創(chuàng):一一哥
前言:
在前面的章節(jié)中,一一哥 已經(jīng)給大家介紹了Spring Security的很多功能,在這些眾多功能中,我們知道其核心功能其實就是認證+授權(quán)。
在前面我們分別基于內(nèi)存模型、基于默認的數(shù)據(jù)庫模型、基于自定義數(shù)據(jù)庫模型實現(xiàn)了認證和授權(quán)功能,但是不管哪種方式,我們對某個接口的攔截限制,都是通過編寫一個SecurityConfig配置類,在該類的configure (Http Security http)方法中,通過http. authorize Requests ( ). antMatchers ("/admin/**")...這樣的代碼進行的權(quán)限控制。
這種權(quán)限控制方法雖然也可以實現(xiàn)對某些接口的攔截或放行,但是不夠靈活,其實Spring Security對接口的攔截或放行的寫法,還有另外的方式,接下來請跟我學習一下吧!
一. 權(quán)限控制方式
在Spring Security 中,我們既可以使用 Spring Security 提供的默認方式進行授權(quán),也可以進行自定義授權(quán),總之在Spring Security中權(quán)限控制的實現(xiàn)方式是比較靈活多樣的。在Spring Security 中,對接口的攔截或放行,有四種常見的權(quán)限控制方式:
利用Ant表達式實現(xiàn)權(quán)限控制;
利用授權(quán)注解結(jié)合SpEl表達式實現(xiàn)權(quán)限控制;
利用過濾器注解實現(xiàn)權(quán)限控制;
利用動態(tài)權(quán)限實現(xiàn)權(quán)限控制。
對上面說到的四種權(quán)限控制方式,我們接下來分別進行講解實現(xiàn)。
二. 利用Ant表達式實現(xiàn)權(quán)限控制
利用Ant表達式的權(quán)限控制方式,是我們之前一直在使用的權(quán)限控制方式,在進行代碼實現(xiàn)之前,我先對這種方式的底層實現(xiàn)進行簡單分析。
1. Spring Security中的權(quán)限控制方法
在Spring Security中,有一個Security Expression Operations 接口,在該接口中定義了一系列的方法,用于用戶權(quán)限的設(shè)置,如下圖:
SecurityExpressionOperations接口中的
這些方法作用如下圖所示:
2. Spring Security中的權(quán)限控制粒度
這個接口有一個SecurityExpressionRoot子類,該類提供了基于表達式的權(quán)限控制實現(xiàn)方式。而這個SecurityExpressionRoot 又有兩個實現(xiàn)子類,分別用于實現(xiàn) URL Web接口粒度的權(quán)限控制和方法粒度的權(quán)限控制,如下圖所示:
3. 代碼實現(xiàn)
從上面的小節(jié)中,我們知道在Spring Security中,支持2種粒度的權(quán)限控制,即URL Web接口粒度 和方法粒度,而我們這里所謂的 Ant表達式授權(quán)控制方式,就是通過Ant表達式來控制 URL 接口的訪問權(quán)限。
那么如果我們需要對URL接口粒度進行權(quán)限控制,按如下代碼即可實現(xiàn):
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.antMatchers("/user/**")
.hasRole("USER")
.antMatchers("/visitor/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.permitAll()
.and()
//對跨域請求偽造進行防護---->csrf:利用用戶帶有登錄狀態(tài)的cookie進行攻擊的手段
.csrf()
.disable();
}
以上代碼中,/admin/ 格式的路徑需要 admin 角色才可以訪問,/user/ 格式的路徑需要 user 角色才可以訪問,/visitor/** 格式的路徑可以直接訪問,其他接口路徑則需要登錄后才能訪問。
三. 利用授權(quán)注解結(jié)合SpEl表達式實現(xiàn)權(quán)限控制
1. 授權(quán)注解
除了可以使用上面的Ant表達式進行授權(quán)實現(xiàn),我們也可以在方法上添加授權(quán)注解來權(quán)限控制,常用的授權(quán)注解有3個:
@PreAuthorize:方法執(zhí)行前進行權(quán)限檢查;
@PostAuthorize:方法執(zhí)行后進行權(quán)限檢查;
@Secured:類似于 @PreAuthorize。
2. 代碼實現(xiàn)
要想利用以上3個授權(quán)注解進行權(quán)限控制,我們首先需要利用@EnableGlobalMethodSecurity注解開啟授權(quán)注解功能,代碼如下:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
...
}
然后在具體的接口方法上利用授權(quán)注解進行權(quán)限控制,代碼如下:
@RestController
public class UserController {
@Secured({"ROLE_USER"})
//@PreAuthorize("principal.username.equals('user')")
@GetMapping("/user/hello")
public String helloUser() {
return "hello, user";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/hello")
public String helloAdmin() {
return "hello, admin";
}
@PreAuthorize("#age>100")
@GetMapping("/age")
public String getAge(@RequestParam("age") Integer age) {
return String.valueOf(age);
}
@GetMapping("/visitor/hello")
public String helloVisitor() {
return "hello, visitor";
}
}
可以看出,這種寫法明顯比利用Ant表達式進行權(quán)限控制更靈活方便,所以開發(fā)時這種寫法很常用。
四. 利用過濾器注解實現(xiàn)權(quán)限控制
1. 過濾器注解簡介
在Spring Security中還提供了另外的兩個注解,即@PreFilter和@PostFilter,這兩個注解可以對集合類型的參數(shù)或返回值進行過濾。使用@PreFilter和@PostFilter時,Spring Security將移除對應表達式結(jié)果為false的元素。
2. @PostFilter的用法
@PostFilter注解主要是用于對集合類型的返回值進行過濾,filterObject是@PostFilter中的一個內(nèi)置表達式,表示集合中的元素對象。
@Slf4j
@RestController
public class FilterController {
/**
* 只返回結(jié)果中id為偶數(shù)的user元素。
* filterObject是@PreFilter和@PostFilter中的一個內(nèi)置表達式,表示集合中的當前對象。
*/
@PostFilter("filterObject.id%2==0")
@GetMapping("/users")
public ListgetAllUser() {
Listusers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "yyg-" + i));
}
return users;
}
}
我們啟動瀏覽器進行測試,可以看到測試接口中只返回了id為偶數(shù)的元素。
3. @PreFilter的用法
使用@PreFilter也可以對集合類型的參數(shù)進行過濾,當@PreFilter標注的方法內(nèi)擁有多個集合類型的參數(shù)時,可以通過@PreFilter的filterTarget屬性來指定當前是針對哪個參數(shù)進行過濾的;而filterObject是@PreFilter中的一個內(nèi)置表達式,表示集合中的元素對象。
為了方便測試,我們在Service層中進行過濾操作,然后在Controller層中進行調(diào)用。
FilterService類中的方法定義:
@Slf4j
@Service
public class FilterService {
/**
* 當@PreFilter標注的方法內(nèi)擁有多個集合類型的參數(shù)時,
* 可以通過@PreFilter的filterTarget屬性來指定當前是針對哪個參數(shù)進行過濾的。
*/
@PreFilter(filterTarget = "ids", value = "filterObject%2==0")
public ListdoFilter(Listids, Listusers) {
log.warn("ids=" + ids.toString());
log.warn("users=" + users.toString());
return ids;
}
}
在Controller中定義一個測試接口:
@Slf4j
@RestController
public class FilterController {
/**
* 只返回結(jié)果中id為偶數(shù)的user元素。
* filterObject是@PreFilter和@PostFilter中的一個內(nèi)置表達式,表示集合中的當前對象。
*/
@PostFilter("filterObject.id%2==0")
@GetMapping("/users")
public ListgetAllUser() {
Listusers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "yyg-" + i));
}
return users;
}
@Autowired
private FilterService filterService;
@GetMapping("/users2")
public ListgetUserInfos() {
Listids = new ArrayList<>();
for (int i = 0; i < 10; i++) {
ids.add(i);
}
Listusers = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add(new User(i, "yyg-" + i));
}
return filterService.doFilter(ids, users);
}
}
我們啟動瀏覽器進行測試,可以看到測試接口中只返回id為偶數(shù)的元素。
4. 代碼結(jié)構(gòu)
下圖是上面案例的代碼結(jié)構(gòu),請參考實現(xiàn):
五. 利用動態(tài)權(quán)限實現(xiàn)權(quán)限控制
我們知道一個標準的RABC, 權(quán)限系統(tǒng)需要支持動態(tài)配置,Spring Security默認是在代碼里約定好權(quán)限,真實的業(yè)務場景里通常需要可以支持動態(tài)配置角色訪問權(quán)限,即在運行時去配置url對應的訪問角色。
而Spring Security中的動態(tài)權(quán)限,主要是通過重寫攔截器和決策器來進行實現(xiàn),最簡單的方法就是自定義一個Filter去完成權(quán)限判斷。其實這里涉及到的代碼,基本和Spring Security關(guān)系不大,主要是在傳統(tǒng)的Filter進行實現(xiàn),我這里就不再進行描述了,感興趣的同學可以自行實現(xiàn)!
至此,我就給各位介紹了Spring Security中的4種進行權(quán)限控制的方式,各位可以結(jié)合自己的項目需求進行選擇。
掃碼關(guān)注公眾號【Java架構(gòu)棧】,獲取全套專欄內(nèi)容及代碼