ruoyi介面許可權校驗

此文章屬於ruoyi項目實戰系列

ruoyi系統在前端主要通過許可權字元包含與否來動態顯示目錄和按鈕。為了防止通過http請求繞過許可權限制,後端介面也需要進行相關許可權設計。

@PreAuthorize使用

由於對@PreAuthorize原理還不夠深入了解,所以此處只粗淺講解在ruoyi項目是如何應用的。
在請求調用介面前,被@preAuthorize註解的介面需要首先通過驗證。通過註解參數value()返回值truefalse來判斷是否有許可權。

public @interface PreAuthorize {  
    String value();  
}

Ruoyi並沒有使用原生的Spel表達式,而是使用了自定義的PermissionService類,通過其中自定義方法hasPermi(String Permission) 來進行許可權判斷。註解使用舉例:@PreAuthorize("@ss.hasPermi('system:menu:list')")

public boolean hasPermi(String permission)  
{  
    if (StringUtils.isEmpty(permission))//用註解就必須有permission值  
    {  
        return false;  
    }  
    LoginUser loginUser = SecurityUtils.getLoginUser();  
    if (StringUtils.isNull(loginUser) ||
     CollectionUtils.isEmpty(loginUser.getPermissions()))  
    {  
        return false;  
    }  
    return hasPermissions(loginUser.getPermissions(), permission);

private boolean hasPermissions(Set<String> permissions, String permission)  
{  
    return permissions.contains(ALL_PERMISSION) ||
     permissions.contains(StringUtils.trim(permission)); //判斷是否持有"所有許可權」字元,或者持有該許可權 
}

介面許可權校驗流程

粗略用兩個例子來講解前端請求如何經過後端介面許可權校驗。

Login匿名請求

  1. Login請求路徑是/login,在過濾器鏈中被AnnoymousAuthenticationFilter添加匿名authentication到Spring上下文里。由於/login請求在SecurityConfig.java里設置成匿名請求,所以可以成功到達SysLoginController

  2. 調用SysLoginService.login方法,關鍵的一行命令:

    Authentication authentication = authenticationManager  
    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
    

    authenticationManager.authenticate()是鉤子方法,在AbstractUserDetailsAuthenticationProvider中實現,會根據傳入的token類型來自動選擇,此處UsernamePasswordAuthenticationToken將由DaoAuthenticationProvider來處理(不清楚的話可以前後打兩個斷點看調用棧)。

  3. DaoAuthenticationProvider中可以看到關鍵的一行:

    UserDetails loadedUser = this.getUserDetailsService()
    .loadUserByUsername(username);
    

    這會調用我們自定義實現的UserDetailsServiceImpl#loadUserByUsername方法(如流程圖所示),獲得user資訊。至於為什麼會使用自定義方法,因為在SecurityConfig.java中進行了配置

    @Override  
    protected void configure(AuthenticationManagerBuilder auth) throws Exception  
    {  
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());  
    }
    
  4. 生成token,然後返回。

已登錄請求

已登錄請求流程較簡單,在流程圖裡的some filters里會通過自定義的JwtAuthenticationFilter,其中會通過token獲得user資訊,然後裝入Spring的上下文,方便提取使用。

曾糾結踩坑的點

由於對SpringSecurity較陌生,雖然功能強大,但其複雜性也是大大提高,所以調試項目的同時翻看了很多入門部落格文章,其中都不約而同的提到了UsernamePasswordAuthenticationFilter,可是我在實戰項目中反覆調試都沒有看到這個過濾器的調用。

原因:Security配置文件需要添加httpSecurity.formLogin()啟用表單登錄才會使用該filter。查看項目使用的所有filter可以使用以下測試程式碼:


class RuoYiApplicationTest {  
    @Autowired  
    private FilterChainProxy filterChainProxy;  
    @Test  
    public void test() {  
        List<SecurityFilterChain> filterChains = filterChainProxy.getFilterChains();  
        for(SecurityFilterChain sfc:filterChains){  
            for(Filter filter:sfc.getFilters()){  
                System.out.println(filter.getClass().getName());  
            }  
        }  
    }  
}