Spring Security源碼分析十三:Spring Security 基於表達式的權限控制
Spring Security是一個能夠為基於Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Control ,DI:Dependency Injection 依賴注入)和AOP(面向切面編程)功能,為應用系統提供聲明式的安全訪問控制功能,減少了為企業系統安全控制編寫大量重複代碼的工作。
前言
spring security 3.0已經可以使用spring el表達式來控制授權,允許在表達式中使用複雜的布爾邏輯來控制訪問的權限。
常見的表達式
Spring Security可用表達式對象的基類是SecurityExpressionRoot。
表達式描述hasRole([role])用戶擁有制定的角色時返回true (Spring security 默認會帶有ROLE_前綴),去除參考Remove the ROLE_hasAnyRole([role1,role2])用戶擁有任意一個制定的角色時返回truehasAuthority([authority])等同於hasRole,但不會帶有ROLE_前綴hasAnyAuthority([auth1,auth2])等同於hasAnyRolepermitAll永遠返回truedenyAll永遠返回falseanonymous當前用戶是anonymous時返回truerememberMe當前勇士是rememberMe用戶返回trueauthentication當前登錄用戶的authentication對象fullAuthenticated當前用戶既不是anonymous也不是rememberMe用戶時返回truehasIpAddress('192.168.1.0/24'))請求發送的IP匹配時返回true部分代碼:
...... private String defaultRolePrefix = "ROLE_"; //ROLE_前綴 /** Allows "permitAll" expression */ public final boolean permitAll = true; //全部true /** Allows "denyAll" expression */ public final boolean denyAll = false; //全部false public final boolean permitAll() { return true; } public final boolean denyAll() { return false; } public final boolean isAnonymous() { //是否是anonymous return trustResolver.isAnonymous(authentication); } public final boolean isRememberMe(){ //是否是rememberme return trustResolver.isRememberMe(authentication); } ......
URL安全表達式
onfig.antMatchers("/person/*").access("hasRole('ADMIN') or hasRole('USER')") .anyRequest().authenticated();這裡我們定義了應用/person/*URL的範圍,該URL只針對擁有ADMIN或者USER權限的用戶有效。
在Web安全表達式中引用bean
config.antMatchers("/person/*").access("hasRole('ADMIN') or hasRole('USER')") .antMatchers("/person/{id}").access("@rbacService.checkUserId(authentication,#id)") .anyRequest() .access("@rbacService.hasPermission(request,authentication)");RbacServiceImpl
@Component("rbacService") @Slf4j public class RbacServiceImpl implements RbacService { /** * uri匹配工具 */ private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override public boolean hasPermission(HttpServletRequest request, Authentication authentication) { log.info("【RbacServiceImpl】 --hasPermission={}", authentication.getPrincipal()); Object principal = authentication.getPrincipal(); boolean hasPermission = false; //有可能是匿名的anonymous if (principal instanceof SysUser) { //admin永遠放回true if (StringUtils.equals("admin", ((SysUser) principal).getUsername())) { hasPermission = true; } else { //讀取用戶所擁有權限所有的URL 在這裡全部返回true Set<String> urls = new HashSet<>(); for (String url : urls) { if (antPathMatcher.match(url, request.getRequestURI())) { hasPermission = true; break; } } } } return hasPermission; } public boolean checkUserId(Authentication authentication, int id) { return true; } }效果如下:
user-gold-cdn.xitu.io/2018/1/30/1…
Method安全表達式
針對方法級別的訪問控制比較複雜,Spring Security提供了四種註解,分別是@PreAuthorize , @PreFilter , @PostAuthorize 和 @PostFilter
使用method註解
開啟方法級別註解的配置@Configuration @EnableGlobalMethodSecurity(prePostEnabled = true) public class MerryyouSecurityConfig extends WebSecurityConfigurerAdapter {配置相應的bean@Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean @ConditionalOnMissingBean(PasswordEncoder.class) public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }在方法上面使用註解/** * 查詢所有人員 */ @PreAuthorize("hasRole('ADMIN')") @ApiOperation(value = "獲得person列表", notes = "") @GetMapping(value = "/persons") public List<Person> getPersons() { return personService.findAll(); }PreAuthorize
@PreAuthorize 註解適合進入方法前的權限驗證
@PreAuthorize("hasRole('ADMIN')") List<Person> findAll();PostAuthorize
@PostAuthorize 在方法執行後再進行權限驗證,適合驗證帶有返回值的權限。Spring EL 提供 返回對象能夠在表達式語言中獲取返回的對象returnObject。
@PostAuthorize("returnObject.name == authentication.name") Person findOne(Integer id);PreAuthorize 針對參數進行過濾
//當有多個對象是使用filterTarget進行標註 @PreFilter(filterTarget="ids", value="filterObject%2==0") public void delete(List<Integer> ids, List<String> usernames) { ... }PostFilter 針對返回結果進行過濾
@PreAuthorize("hasRole('ADMIN')") @PostFilter("filterObject.name == authentication.name") List<Person> findAll();效果如下:
raw.githubusercontent.com/longfeizhen…
作者名稱:Spring Security