此文章屬於ruoyi專案實戰系列
ruoyi系統在前端主要通過許可權字元包含與否來動態顯示目錄和按鈕。為了防止通過http請求繞過許可權限制,後端介面也需要進行相關許可權設計。
由於對@PreAuthorize
原理還不夠深入瞭解,所以此處只粗淺講解在ruoyi專案是如何應用的。
在請求呼叫介面前,被@preAuthorize
註解的介面需要首先通過驗證。通過註解引數value()
返回值true
和false
來判斷是否有許可權。
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請求路徑是/login
,在過濾器鏈中被AnnoymousAuthenticationFilter
新增匿名authentication
到Spring上下文裡。由於/login
請求在SecurityConfig.java
裡設定成匿名請求,所以可以成功到達SysLoginController
。
呼叫SysLoginService.login
方法,關鍵的一行命令:
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
authenticationManager.authenticate()
是勾點方法,在AbstractUserDetailsAuthenticationProvider
中實現,會根據傳入的token型別來自動選擇,此處UsernamePasswordAuthenticationToken
將由DaoAuthenticationProvider
來處理(不清楚的話可以前後打兩個斷點看呼叫棧)。
在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());
}
生成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());
}
}
}
}