從零開始SpringCloud Alibaba電商系統(十一)——spring security完善之動態url控制

零、系列

歡迎來嫖從零開始SpringCloud Alibaba電商系列:

  1. 從零開始SpringCloud Alibaba電商系統(一)——Alibaba與Nacos服務註冊與發現
  2. 從零開始SpringCloud Alibaba電商系統(二)——Nacos配置中心
  3. 從零開始SpringCloud Alibaba電商系統(三)——Sentinel流量防衛兵介紹、流量控制demo
  4. 從零開始SpringCloud Alibaba電商系統(四)——Sentinel的fallback和blockHandler
  5. 從零開始SpringCloud Alibaba電商系統(五)——Feign Demo,Sentinel+Feign實現多節點間熔斷/服務降級
  6. 從零開始SpringCloud Alibaba電商系統(六)——Sentinel規則持久化到Nacos配置中心
  7. 從零開始SpringCloud Alibaba電商系統(七)——Spring Security實現登錄認證、權限控制
  8. 從零開始SpringCloud Alibaba電商系統(八)——用一個好看的Swagger接口文檔
  9. 從零開始SpringCloud Alibaba電商系統(九)——基於Spring Security OAuth2實現SSO-認證服務器(非JWT)
  10. 從零開始SpringCloud Alibaba電商系統(十)——基於Redis Session的認證鑑權

一、動態url控制

前幾章,我們完善了權限系統的SSO登錄認證以及redis共享session獲取,但是對於一個權限要求靈活的後臺管理系統來說還不夠。

回想一下,我們之前是怎麼控制一個接口的權限的:

  1. 在controller接口的腦袋上定義@PreAuthorize註解並聲明訪問規則。
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    @GetMapping("apply02")
    public String applyOrder02(){
        return "權限專用測試";
    }
  1. 在spring security的配置類–configure(http)方法中配置url以及訪問規則,http配置url訪問規則的方式很靈活,下面只是舉個例子。
			http .authorizeRequests()
                    .antMatchers("/test/*","/order/*")
                    .hasAnyRole("admin")

可以發現一個問題,url的權限控制要麼一個個硬編碼在接口腦袋上,要麼一個個硬編碼在配置類,有沒有什麼方法可以讓我們使用數據庫來存儲這些權限規則並動態的應用上呢?

這就是我們今天第一個要解決的問題,實際上網上有很多相關資料,但是基本上都是一樣的,都是通過增加
一個spring security的過濾鏈中增加一個Filter,然後自定義一個權限決定器AccessDecisionManager和一個
權限數據加載器FilterInvocationSecurityMetadataSource,我覺得這種方式太過麻煩,於是自己想了一種
方法來更簡單靈活的配置我們的權限系統。

AccessDecisionManager核心的方法是decide,它傳入用戶信息auth和當前訪問的request進行一些決策來決定當前用戶是否可以訪問當前資源。它一般的策略是通過權限加載器獲得一些數據,然後根據這些數據進行一票通過、一票否決、多數通過的策略等,最後的結果是返回是否有權訪問有興趣的同學可以goole/百度一下具體的使用和原理。

而我直接將decide的方法換了一個定義:你已經給了我用戶的權限信息和當前訪問的url,我完全可以直接在這裏根據用戶的權限(在之前設計中用戶的權限集合存放的就是資源的url)和url做一個比對,存在即可通過。
這種方法可以極大的減少代碼量,只需要重寫一個AccessDecisionManager,然後將其通過http配置一下就可以了。

二、具體實現

  1. 上文的基礎上,增加一個實現了AccessDecisionManager接口的MallAccessDecisionManager類,按照上述邏輯,實現類如下:
    這裏加了一個ant規則匹配url,靈活一些。
@Component
public class MallAccessDecisionManager implements AccessDecisionManager {

    /**
     *  若用戶的權限中包含當前路徑所需權限,則可以通過,否則認證異常
     * @param authentication 用戶認證信息,包含用戶所擁有的權限
     * @param object
     * @param configAttributes 訪問當前路徑所需要的權限
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        if(configAttributes==null){
            return;
        }
        String requestUrl = ((FilterInvocation)object).getRequestUrl() ;
        AntPathMatcher antPathMatcher = new AntPathMatcher();

        for (GrantedAuthority authority : authentication.getAuthorities()) {
            // 字符串等於匹配 TODO  動態url
            if( authority.getAuthority().equals( requestUrl )) { return; }
            // ant匹配
            if( antPathMatcher.match(authority.getAuthority(),requestUrl) ) {return;}
            // 對已登錄用戶放開屬於當前服務器靜態資源,其實只有swagger資源
            boolean hasPower = authentication.isAuthenticated() && !authority.getAuthority().equals("ROLE_ANONYMOUS")
                    && (antPathMatcher.match("/webjars/**",requestUrl) ||
                    (antPathMatcher.match("/swagger-resources",requestUrl))
                    || (antPathMatcher.match("/v2/api-docs",requestUrl))
                    || (antPathMatcher.match("/doc.html",requestUrl))
            );
            if( hasPower  ) {
                return;
            }
        }
        throw new AccessDeniedException("抱歉,您沒有訪問權限");
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }
}
  1. 然後在WebSecurityConfiguration配置AccessDecisionManager的Bean並將Bean配置到http中。
  @Bean
    public AccessDecisionManager accessDecisionManager(){
        return new MallAccessDecisionManager();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .anyRequest().authenticated()
                    .accessDecisionManager(accessDecisionManager())
                .and()
                .formLogin()
                    .defaultSuccessUrl("/doc.html")
                .and() ;
    }

就這麼簡單,完事。

  1. 插入測試數據,在數據庫權限表添加記錄並關聯到角色,角色關聯到用戶。
    4.
    ps:這裏只貼了一個權限表,其他表數據很簡單不再貼出來,表結構在前文有說明。

  2. 打開 http://localhost:8082/login,登錄。
    在這裏插入圖片描述
    訪問接口,可以發現只有無參訪問GET接口可以成功,配置成功。
    剩下的數據就靠大家自由發揮了。

三、demo地址

完整代碼:
https://github.com/flyChineseBoy/lel-mall/tree/master/mall10

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章