Spring Security原始碼分析十三:Spring Security 基於表示式的許可權控制

鄭龍飛發表於2018-01-31

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]) 使用者擁有任意一個制定的角色時返回true
hasAuthority([authority]) 等同於hasRole,但不會帶有ROLE_字首
hasAnyAuthority([auth1,auth2]) 等同於hasAnyRole
permitAll 永遠返回true
denyAll 永遠返回false
anonymous 當前使用者是anonymous時返回true
rememberMe 當前勇士是rememberMe使用者返回true
authentication 當前登入使用者的authentication物件
fullAuthenticated 當前使用者既不是anonymous也不是rememberMe使用者時返回true
hasIpAddress('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;
    }
}
複製程式碼

效果如下:

https://user-gold-cdn.xitu.io/2018/1/30/16147b3f89291c64?w=1190&h=601&f=gif&s=1412669
https://user-gold-cdn.xitu.io/2018/1/30/16147b3f89291c64?w=1190&h=601&f=gif&s=1412669

Method安全表示式

針對方法級別的訪問控制比較複雜,Spring Security提供了四種註解,分別是@PreAuthorize , @PreFilter , @PostAuthorize@PostFilter

使用method註解

  1. 開啟方法級別註解的配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MerryyouSecurityConfig extends WebSecurityConfigurerAdapter {
複製程式碼
  1. 配置相應的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();
    }
複製程式碼
  1. 在方法上面使用註解
 /**
     * 查詢所有人員
     */
    @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();
複製程式碼

效果如下:

https://i.iter01.com/images/3961d476b84b414cd8aabfed1ea3d07d0f9ff2980ac7c7a489a176acdf37bf11.gif
https://i.iter01.com/images/3961d476b84b414cd8aabfed1ea3d07d0f9ff2980ac7c7a489a176acdf37bf11.gif

程式碼下載

從我的 github 中下載,github.com/longfeizhen…

相關文章