引言
項(xiàng)目登陸校驗(yàn)是一個(gè)項(xiàng)目的根基,鑒權(quán)又是根基之本。先結(jié)合實(shí)際的項(xiàng)目背景來解釋什么是鑒權(quán):有選課系統(tǒng),會(huì)分成學(xué)生登陸,老師登陸,超級(jí)管理員登陸。那不同的人登陸的時(shí)候看到的菜單,內(nèi)容會(huì)不一樣。此時(shí)就不能只是鑒別賬號(hào)密碼是否正確,還要帶上登陸人相應(yīng)的操作權(quán)限。
當(dāng)然,鑒權(quán)可以不一定發(fā)生在登陸的時(shí)候,也可以發(fā)生在請求接口的時(shí)候。如果是只在接口層面鑒權(quán)的話,那菜單的展示只能不做鑒權(quán),也就是不同的賬號(hào)進(jìn)入看到的所有欄目都是完整的,只是沒有點(diǎn)擊和操作權(quán)限。在登陸的時(shí)候做鑒權(quán),就可以把菜單欄目整個(gè)都放在后端控制。
下面我就來談?wù)?,?xiàng)目中常用到的鑒權(quán)幾種形式:
鑒權(quán)的三種方式
一、采用springSecurity內(nèi)部的決策管理器和投票器實(shí)現(xiàn)
參考網(wǎng)址:
http://blog.51cto.com/5148737/2308131
https://blog.csdn.net/sinat_29899265/article/details/80771330
PS:個(gè)人覺得這種方式比較麻煩,不容易理解與配置,不推薦。
二、采用自行編寫Filter或者Interceptor去攔截
關(guān)于二者的書寫方法可以參考博主之前的博客:Fliter和Interceptor區(qū)別與@Autowired報(bào)錯(cuò)(空指針)解決,還是推薦采用springMVC的Interceptor,可以很便捷的對url進(jìn)行模糊的通配符**匹配攔截。
三、 利用Spring Security利用@PreAuthorize注解,去對類或者接口判斷是否具有訪問權(quán)限。
具體有兩種寫法:
- 一種是采用默認(rèn)提供的函數(shù)@PreAuthorize("hasAuthority('BBBB','AAAA'))。該方法要求在登陸鑒權(quán)那塊,需要將AAAA和BBBB權(quán)限給加入到springsecurity的User對象中。
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("BBBB"));
authorities.add(new SimpleGrantedAuthority("AAAA"));
//在創(chuàng)建user對象的時(shí)候要把a(bǔ)uthorities給注入
Account account = new Account(id, username, "empty", true,
true, true, true, authorities);
其中Account繼承User類(springSecurity自帶的,實(shí)現(xiàn)了UserDetails接口的類),調(diào)用父類的初始化方法。
public Account(Long accountId, String username, String password, boolean enabled, boolean accountNonExpired,
boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.accountId = accountId;
}
private final Set<GrantedAuthority> authorities; 是springsecurity里面user的成員變量。 這樣子user對象帶上了BBBB和AAAA的權(quán)限,然后再將整個(gè)user給注冊到springsecurity中:
public static void registerAuth(User account, ServletContext sc, HttpServletRequest request)
throws AccessDeniedException {
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(account, "empty",
account.getAuthorities());
setDetails(request, auth);
//重點(diǎn)在這句
SecurityContextHolder.getContext().setAuthentication(auth);
HttpSession session = request.getSession();
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
SecurityContextHolder.getContext());
WebApplicationContext webApplicationContext = WebApplicationContextUtils.getWebApplicationContext(sc);
webApplicationContext.publishEvent(new InteractiveAuthenticationSuccessEvent(auth, SecurityUtils.class));
}
那么就可以利用spring的上下文取出相應(yīng)的Account對象的權(quán)限:
public static Account getLoginAccount() {
//重點(diǎn)在這句
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
return null;
}
Object principal = authentication.getPrincipal();
if (principal == null) {
return null;
}
if (principal instanceof Account) {
return (Account) principal;
}
return null;
}
- 另外一種是采用自行撰寫函數(shù)去控制@PreAuthorize("@checkPhoneAuthorityUtil.check()")。該方法要求在相應(yīng)的類在spring容器之中,并且返回的是boolean類型。如果返回為false則無權(quán)限。
@Component("checkPhoneAuthorityUtil")
public class CheckPhoneAuthorityUtil {
public Boolean check() {
Account account = SecurityUtils.getLoginAccount();
if (StringUtils.isBlank(account.getPhone())){
throw new FantuanRuntimeException("請綁定手機(jī)號(hào)", HttpServletResponse.SC_UNAUTHORIZED);
}
return Boolean.TRUE;
}
}
- 如果要多種方式去控制權(quán)限的話,只需要在兩種權(quán)限之間用and相連:
@PostMapping(value = "/yktest")
@PreAuthorize("hasAnyAuthority('AAAA','BBBB') and @checkPhoneAuthorityUtil .check()")
@ResponseBody
public RestResponse<String> yktest() throws Exception {
//鑒權(quán)demo
return RestResponse.ok("執(zhí)行完成");
}
注:@PreAuthorize不僅可以注解在方法上,還可以注解在類上。
總結(jié)
雖然springSecurity給我們封裝了很好的一個(gè)接口,但是用起來比較笨重,并且靈活性感覺不夠,尤其是采用方法一的形式。并且如果在antMatchers()里面配置的話,就不會(huì)進(jìn)行鑒權(quán)了。所以當(dāng)需要多種鑒權(quán)等比較靈活的情況下,還是推薦自行撰寫攔截器,或者自行編寫的方法用在 @PreAuthorize中。