SpringSecurity原理剖析與許可權系統設計

来源:https://www.cnblogs.com/fanzhidongyzby/archive/2019/09/29/11610334.html
-Advertisement-
Play Games

Spring Secutity和Apache Shiro是Java領域的兩大主流開源安全框架,也是許可權系統設計的主要技術選型。本文主要介紹Spring Secutity的實現原理,並基於Spring Secutity設計基於RBAC的許可權系統。 ...


Spring Secutity和Apache Shiro是Java領域的兩大主流開源安全框架,也是許可權系統設計的主要技術選型。本文主要介紹Spring Secutity的實現原理,並基於Spring Secutity設計基於RBAC的許可權系統。

一、技術選型

為何把Spring Secutity作為許可權系統的技術選型,主要考慮了以下幾個方面:

  1. 數據鑒權的能力:Spring Secutity支持數據鑒權,即細粒度許可權控制。
  2. Spring生態基礎:Spring Secutity可以和Spring生態無縫集成。
  3. 多樣認證能力:Spring Secutity支持多樣認證方式,如預認證方式可以與第三方認證系統集成。
Spring Security Apache Shiro
認證 支持多種認證方式(如密碼、匿名、預認證 簡單登錄認證
鑒權 功能鑒權、數據鑒權 功能鑒權
多源適配 Mem、JDBC、DAO、LDAP、
OpenID、OAuth等
LDAP、JDBC、Kerberos、
ActiveDirectory等
加密 支持多種加密方式 簡單加密方式
運行環境 依賴Spring 可獨立運行
開放性 開源、Spring生態基礎 開源
複雜度 複雜、較重 簡單、靈活

二、核心架構

許可權系統一般包含兩大核心模塊:認證(Authentication)和鑒權(Authorization)。

  • 認證:認證模塊負責驗證用戶身份的合法性,生成認證令牌,並保存到服務端會話中(如TLS)。
  • 鑒權:鑒權模塊負責從服務端會話內獲取用戶身份信息,與訪問的資源進行許可權比對。

官方給出的Spring Security的核心架構圖如下:

核心架構解讀:

  • AuthenticationManager:負責認證管理,解析用戶登錄信息(封裝在Authentication),讀取用戶、角色、許可權信息進行認證,認證結果被回填到Authentication,保存在SecurityContext。
  • AccessDecisionManager:負責鑒權投票表決,彙總投票器的結果,實現一票通過(預設)、多票通過、一票否決策略。
  • SecurityInterceptor:負責許可權攔截,包括Web URL攔截和方法調用攔截。通過ConfigAttributes獲取資源的描述信息,藉助於AccessDecisionManager進行鑒權攔截。
  • SecurityContext:安全上下文,保存認證結果。提供了全局上下文、線程繼承上下文、線程獨立上下文(預設)三種策略。
  • Authentication:認證信息,保存用戶的身份標示、許可權列表、證書、認證通過標記等信息。
  • SecuredResource:被安全管控的資源,如Web URL、用戶、角色、自定義領域對象等。
  • ConfigAttributes:資源屬性配置,描述安全管控資源的信息,為SecurityInterceptor提供攔截邏輯的輸入。

三、設計原理

通過對源碼的分析,我把Spring Security的核心領域模型設計整理如下:

全局抽象模型解讀:

  • 配置:AuthenticationConfiguration負責認證系統的全局配置,GlobalMethodSecurityConfiguration負責方法調用攔截的全局配置。
  • 構建:AuthenticationConfiguration通過AuthenticationManagerBuilder構建認證管理器AuthenticationManager,GlobalMethodSecurityConfiguration會自動初始化AbstractSecurityInterceptor進行方法調用攔截。
  • Web攔截:HttpSecurity對Web進行安全配置,內置了大量GenericFilterBean過濾器對URL進行攔截。負責認證的過濾器會通過AuthenticationManager進行認證,並將認證結果保存到SecurityContext。
  • 方法攔截:Spring通過AOP技術(cglib/aspectj)對標記為@PreAuthorize、@PreFilter、@PostAuthorize、@PostFilter等註解的方法進行攔截,通過AbstractSecurityInterceptor調用AuthenticationManager進行身份認證(如果必要的話)。
  • 認證:認證管理器AuthenticationManager內置了多種認證器AuthenticationProvider,只要其中一個認證通過,認證便成功。不同的AuthenticationProvider獲取各自需要的信息(HTTP請求、資料庫查詢、遠程服務等)進行認證,認證結果全部封裝在Authentication。需要載入用戶、角色、許可權信息的認證器(如密碼認證、預認證等)需要對接UserDetailsManager介面實現用戶CRUD功能。
  • 鑒權:許可權攔截器AbstractSecurityInterceptor通過讀取不同的SecurityMetadataSource載入需要被鑒權資源的描述信息ConfigAttribute,然後把認證信息Authentication、資源描述ConfigAttribute、資源對象本身傳遞給AccessDecisionManager進行表決。AccessDecisionManager內置了多個投票器AccessDecisionVoter,投票器會將鑒權信息中的ConfigAttribute轉換為SpringEL的格式,通過表達式處理器SecurityExpressionHandler執行基於表達式的鑒權邏輯,鑒權邏輯會通過反射的方式轉發到SecurityExpressionRoot的各個操作上去。
  • 定製:通過WebSecurityConfigureAdapter可以定製HTTP安全配置HttpSecurity和認證管理器生成器AuthenticationManagerBuilder;通過AbstractPreAuthenticatedProcessingFilter可以定製預認證過濾器;通過UserDetailsManager和UserDetails介面可以對接自定義數據源;通過GrantedAuthority定製許可權信息;通過PermissionEvaluator可以定製自定義領域模型的訪問控制邏輯。

四、應用集成

理清Spring Security的定製點後,就可以在系統內部集成Spring Security了。

這裡使用預認證的方式,以適配第三方認證系統。AbstractPreAuthenticatedProcessingFilter提供了預認證的擴展點,基於該抽象類實現一個自定義認證過濾器。

public class MyPreAuthFilter extends AbstractPreAuthenticatedProcessingFilter {
    @Override
    protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
        // 從第三方系統獲取用戶ID
        return userId;
    }

    @Override
    protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
        return "";
    }
}

Spring Security會根據預認證過濾器getPreAuthenticatedPrincipal返回的用戶ID信息,載入用戶角色等初始信息。這裡需要實現UserDetailsManager介面,提供用戶信息管理器。

@Service
public class MyUserManager implements UserDetailsManager {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 從資料庫載入用戶信息
        return user;
    }
    
    // 其他管理介面
}

UserDetails內包含了GrantedAuthority介面類型的許可權信息抽象,一般可以基於它自定義角色和許可權。Spring Security使用一種介面形式表達角色和許可權,角色和許可權的差別是角色的ID是以"ROLE_"為首碼。

public class MyRole implements GrantedAuthority {
    private final String role;

    @Override
    public String getAuthority() {
        return "ROLE_" + role;
    }
}

public class MyAuthority implements GrantedAuthority {
    private final String authority;

    @Override
    public String getAuthority() {
        return authority;
    }
}

接下來註冊自定義認證過濾器和用戶管理器,這裡需要實現WebSecurityConfigurerAdapter進行Web安全配置。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, mode = AdviceMode.PROXY)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    UserDetailsManager userDetailsManager;

    @Bean
    protected AuthenticationProvider createPreAuthProvider() {
        // 註冊用戶管理器
        PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
        provider.setPreAuthenticatedUserDetailsService(new UserDetailsByNameServiceWrapper<>(userDetailsManager));
        return provider;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 註冊預認證過濾器
        http.addFilter(new MyPreAuthFilter(authenticationManager()));
    }
}

這樣,最簡單的Spring Security框架集成內系統內部已經完成了。在系統的任意服務介面上可以使用如下方式進行鑒權。

public interface MyService {
    @PreAuthorize("hasAuthority('QUERY')")
    Object getById(String id);
    
    @PreAuthorize("hasRole('ADMIN')")
    void deleteById(String id);
}

PreAuthorize註解表示調用前鑒權,Spring使用預設使用動態代理技術生成鑒權邏輯。註解內配置了SpringEL表達式來定製鑒權方式。上述代碼中,hasAuthority會檢查用戶是否有QUERY許可權,hasRole會檢查用戶是否有ADMIN角色。

使用動態代理的方式進行AOP,只允許在介面層面進行許可權攔截,如果想在任意的方法上進行許可權攔截,那麼就需要藉助於AspectJ的方式進行AOP。首先將註解EnableGlobalMethodSecurity的mode設置為AdviceMode.ASPECTJ,然後添加JVM啟動參數,這樣就可以在任意方法上使用Spring Security的註解了。

-javaagent:/path/to/org/aspectj/aspectjweaver/1.9.4/aspectjweaver-1.9.4.jar

以上還是只是以用戶的身份信息(角色/許可權)進行許可權,靈活度有限,也發揮不了Spring Security的數據鑒權的能力。要使用數據鑒權,需要實現一個Spring Bean。

@Component
public class MyPermissionEvaluator implements PermissionEvaluator {
    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
        // 自定義數據鑒權
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
        // 自定義數據鑒權
        return false;
    }
}

PermissionEvaluator會被自動註冊到Spring Security框架,並允許在註解內使用如下方式進行鑒權。

@PreAuthorize("hasPermission(#id, 'QUERY')")
Object func1(String id) {
}

@PreAuthorize("hasPermission(#id, 'TABLE', 'QUERY')")
Object func2(String id) {
}

其中,func1的註解表示校驗用戶是否對id有QUERY許可權,代碼邏輯路由到MyPermissionEvaluator的第一個介面。func2的註解表示校驗用戶是否對TABLE類型的id有QUERY許可權,代碼邏輯路由到MyPermissionEvaluator的第二個介面。PermissionEvaluator提供了許可權系統中數據鑒權的擴展點,稍後會描述如何利用該擴展點定製基於RBAC的許可權系統。

五、許可權系統

構建基於RBAC(Role Based Access Control)的許可權系統,需要明確用戶、角色、許可權、資源這幾個核心的概念類的含義和它們之間的關係。

  • 資源:許可權系統內需要安全控制的客體,一般是系統內的數據或功能。
  • 許可權:描述了資源上的操作抽象,一般是一種動作。
  • 授權:是許可權和資源的組合,表示對資源的某一個操作。
  • 角色:描述了一組授權的集合,表示一類特殊概念的功能集。
  • 用戶:許可權系統的主體,一般是當前系統的訪問用戶,用戶可以擁有多種角色。

以下是我們設計的基於RABC的許可權核心領域模型:

一般情況下,系統內需要許可權管控的資源是無法用戶自定義的,因為資源會耦合大量的業務邏輯,所以我們提供了自 資源工廠,通過配置化的方式構建業務模塊所需的資源。而用戶、角色、許可權,以及授權記錄都是可以通過相應的管理器進行查詢更新。

另外,資源抽象允許表達資源的繼承和組合關係,繼而表達更複雜的資源模型,資源統一鑒權的流程為:

  • 執行鑒權時,首先看資源是原子資源還是組合資源。
  • 對於原子資源,先查詢是否有授權記錄,再查看角色預授權是否包含當前授權,存在一種便成功。
  • 沒有授權記錄和角色預授權的原子資源,嘗試用父資源(如果有的話)代替鑒權,否則鑒權失敗。
  • 對於組合資源,先進行資源展開,獲取子資源列表。
  • 遍歷子資源列表,並依次對子資源進行鑒權,子資源鑒權結果彙總後,即組合資源鑒權結果。

綜上,基於統一資源抽象和資源配置化構建,可以實現資源的統一構建,繼而實現統一鑒權。

六、總結回顧

本文從Spring Security的架構和原理出發,描述了開源安全框架對於認證和鑒權模塊的設計思路和細節。並提供了系統內集成Spring Security的方法,結合RBAC通用許可權系統模型,討論了統一資源構建和統一鑒權的設計和實現。如果你也需要設計一個新的許可權系統,希望本文對你有所幫助。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 創建一個測試用的微服務項目HelloWorld 創建項目 編寫服務代碼 編輯配置文件application.properties: 測試運行 源碼 "Github倉庫:https://github.com/sunweisheng/spring cloud example" ...
  • 目錄: 一.字元編碼 二.字元串格式化 三.進位轉換 四.數據類型及其操作 五.字元串轉換 六.列表 七.元組 八.字典 一.字元編碼: 電腦由美國人發明,最早的字元編碼為ASCII,只規定了英文字母數字和一些特殊字元與數字的對應關係。最多只能用 8 位來表示(一個位元組),即:2**8 = 256 ...
  • 樹的基本概念和常用術語 節點的度:一個結點的兒子結點個數稱為該節點的度 樹的度:一棵樹的度是指該樹中結點的最大度數。如上圖的樹的度是3 葉節點或終端節點:度為零的節點。如上圖中E,I,J,C,G,H是葉節點 非終端節點或分支節點:度不為零的節點。除根節點外的分支節點都叫做內部節點。 路徑:若存在樹中 ...
  • 今天,我們將通過 topic構建一些彼此非同步通信的微服務。我們使用 框架,它為與 集成提供專門的庫。讓我們簡要介紹一下示例系統的架構。我們有四個微型服務: ,`行程服務 司機服務 乘客服務 Kafka`實例。 我們系統的主要目標是為客戶安排行程。訂單服務應用程式還充當網關。它接收來自客戶的請求,保存 ...
  • 在剛開始學習構造器時並不知道可以在什麼具體的地方用到,直到前幾天在寫一個書簽應用時,涉及到添加書簽功能。大致的思路是這樣的, 點擊添加書簽按鈕,先向資料庫里插入一個空的書簽數據,當然id是自增的,然後刷新頁面,根據id載入出一個空的表單,填寫數據後再更新表單,提交數據。 這其中就涉及到一個構造器的問 ...
  • 面試題 分庫分表之後,id 主鍵如何處理? 面試官心理分析 其實這是分庫分表之後你必然要面對的一個問題,就是 id 咋生成?因為要是分成多個表之後,每個表都是從 1 開始累加,那肯定不對啊,需要一個 全局唯一 的 id 來支持。所以這都是你實際生產環境中必須考慮的問題。 面試題剖析 基於資料庫的實現 ...
  • 一、使用限定註解;二、自定義限定註解;三、自定義bean的生命周期; ...
  • 慢查詢原因分析 由於 Redis 是單線程的,它內部維護了一個命令隊列,所以當有耗時的命令出現時,比如 ,後面的命令會被阻塞,通查查出慢查詢可以對服務進一步優化。 1. 設置慢查詢閥值:預設10 毫秒,以微秒為單位 6379 config set slowlog log slower than 10 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...