SpringSecurity原理剖析與權限系統設計

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

1、技術選型

爲什麼把Spring Secutity做爲權限系統的技術選型,主要考慮瞭如下幾個方面:java

  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生態基礎 開源
複雜度 複雜、較重 簡單、靈活

2、核心架構

權限系統通常包含兩大核心模塊:認證(Authentication)和鑑權(Authorization)。spring

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

官方給出的Spring Security的核心架構圖以下:
數據庫

核心架構解讀:express

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

3、設計原理

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

全局抽象模型解讀:bash

  • 配置: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能夠定製自定義領域模型的訪問控制邏輯。

4、應用集成

理清Spring Security的定製點後,就能夠在系統內部集成Spring Security了。架構

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

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的權限系統。

5、權限系統

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

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

如下是咱們設計的基於RABC的權限核心領域模型:

通常狀況下,系統內須要權限管控的資源是沒法用戶自定義的,由於資源會耦合大量的業務邏輯,因此咱們提供了自 資源工廠,經過配置化的方式構建業務模塊所需的資源。而用戶、角色、權限,以及受權記錄都是能夠經過相應的管理器進行查詢更新。

另外,資源抽象容許表達資源的繼承和組合關係,繼而表達更復雜的資源模型,資源統一鑑權的流程爲:

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

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

6、總結回顧

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

相關文章
相關標籤/搜索