Spring Security 權(quán)限鑒定基礎(chǔ)

2020-07-14 10:48 更新

Spring Security 的權(quán)限鑒定是由 AccessDecisionManager 接口負(fù)責(zé)的。具體來說是由其中的 decide()方法負(fù)責(zé),其定義如下。

    void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
        throws AccessDeniedException, InsufficientAuthenticationException;

如你所見,該方法接收三個參數(shù),第一個參數(shù)是包含當(dāng)前用戶信息的 Authentication 對象;第二個參數(shù)表示當(dāng)前正在請求的受保護(hù)的對象,基本上來說是 MethodInvocation(使用 AOP)、JoinPoint(使用 Aspectj)和 FilterInvocation(Web 請求)三種類型;第三個參數(shù)表示與當(dāng)前正在訪問的受保護(hù)對象的配置屬性,如一個角色列表。

Spring Security 的 AOP Advice 思想

對于使用 AOP 而言,我們可以使用幾種不同類型的 advice:before、after、throws 和 around。其中 around advice 是非常實用的,通過它我們可以控制是否要執(zhí)行方法、是否要修改方法的返回值,以及是否要拋出異常。Spring Security 在對方法調(diào)用和 Web 請求時也是使用的 around advice 的思想。在方法調(diào)用時,可以使用標(biāo)準(zhǔn)的 Spring AOP 來達(dá)到 around advice 的效果,而在進(jìn)行 Web 請求時是通過標(biāo)準(zhǔn)的 Filter 來達(dá)到 around advice 的效果。

對于大部分人而言都比較喜歡對 Service 層的方法調(diào)用進(jìn)行權(quán)限控制,因為我們的主要業(yè)務(wù)邏輯都是在 Service 層進(jìn)行實現(xiàn)的。如果你只是想保護(hù) Service 層的方法,那么使用 Spring AOP 就可以了。如果你需要直接保護(hù)領(lǐng)域?qū)ο螅敲茨憧梢钥紤]使用 Aspectj。

你可以選擇使用 Aspectj 或 Spring AOP 對方法調(diào)用進(jìn)行鑒權(quán),或者選擇使用 Filter 對 Web 請求進(jìn)行鑒權(quán)。當(dāng)然,你也可以選擇使用這三種方式的任意組合進(jìn)行鑒權(quán)。通常的做法是使用 Filter 對 Web 請求進(jìn)行一個比較粗略的鑒權(quán),輔以使用 Spring AOP 對 Service 層的方法進(jìn)行較細(xì)粒度的鑒權(quán)。

AbstractSecurityInterceptor

AbstractSecurityInterceptor 是一個實現(xiàn)了對受保護(hù)對象的訪問進(jìn)行攔截的抽象類,其中有幾個比較重要的方法。beforeInvocation()方法實現(xiàn)了對訪問受保護(hù)對象的權(quán)限校驗,內(nèi)部用到了 AccessDecisionManager 和 AuthenticationManager;finallyInvocation()方法用于實現(xiàn)受保護(hù)對象請求完畢后的一些清理工作,主要是如果在 beforeInvocation() 中改變了 SecurityContext,則在 finallyInvocation()中需要將其恢復(fù)為原來的SecurityContext,該方法的調(diào)用應(yīng)當(dāng)包含在子類請求受保護(hù)資源時的 finally 語句塊中;afterInvocation()方法實現(xiàn)了對返回結(jié)果的處理,在注入了 AfterInvocationManager 的情況下默認(rèn)會調(diào)用其 decide()方法。AbstractSecurityInterceptor 只是提供了這幾種方法,并且包含了默認(rèn)實現(xiàn),具體怎么調(diào)用將由子類負(fù)責(zé)。每一種受保護(hù)對象都擁有繼承自 AbstractSecurityInterceptor 的攔截器類,MethodSecurityInterceptor將用于調(diào)用受保護(hù)的方法,而 FilterSecurityInterceptor 將用于受保護(hù)的 Web 請求。它們在處理受保護(hù)對象的請求時都具有一致的邏輯,具體的邏輯如下。

  1. 先將正在請求調(diào)用的受保護(hù)對象傳遞給 beforeInvocation()方法進(jìn)行權(quán)限鑒定。
  2. 權(quán)限鑒定失敗就直接拋出異常了。
  3. 鑒定成功將嘗試調(diào)用受保護(hù)對象,調(diào)用完成后,不管是成功調(diào)用,還是拋出異常,都將執(zhí)行 finallyInvocation()。
  4. 如果在調(diào)用受保護(hù)對象后沒有拋出異常,則調(diào)用 afterInvocation()。

以下是 MethodSecurityInterceptor 在進(jìn)行方法調(diào)用的一段核心代碼。

    public Object invoke(MethodInvocation mi) throws Throwable {
        InterceptorStatusToken token = super.beforeInvocation(mi);

        Object result;
        try {
            result = mi.proceed();
        } finally {
            super.finallyInvocation(token);
        }
        returnsuper.afterInvocation(token, result);
    }

ConfigAttribute

AbstractSecurityInterceptor 的 beforeInvocation()方法內(nèi)部在進(jìn)行鑒權(quán)的時候使用的是注入的 AccessDecisionManager 的 decide() 方法進(jìn)行的。如前所述,decide()方法是需要接收一個受保護(hù)對象對應(yīng)的 ConfigAttribute 集合的。一個 ConfigAttribute 可能只是一個簡單的角色名稱,具體將視 AccessDecisionManager 的實現(xiàn)者而定。AbstractSecurityInterceptor 將使用一個 SecurityMetadataSource 對象來獲取與受保護(hù)對象關(guān)聯(lián)的 ConfigAttribute 集合,具體 SecurityMetadataSource 將由子類實現(xiàn)提供。ConfigAttribute 將通過注解的形式定義在受保護(hù)的方法上,或者通過 access 屬性定義在受保護(hù)的 URL 上。例如我們常見的 就表示將 ConfigAttribute ROLE_USER 和 ROLE_ADMIN 應(yīng)用在所有的 URL 請求上。對于默認(rèn)的 AccessDecisionManager 的實現(xiàn),上述配置意味著用戶所擁有的權(quán)限中只要擁有一個 GrantedAuthority 與這兩個 ConfigAttribute 中的一個進(jìn)行匹配則允許進(jìn)行訪問。當(dāng)然,嚴(yán)格的來說 ConfigAttribute 只是一個簡單的配置屬性而已,具體的解釋將由 AccessDecisionManager 來決定。

RunAsManager

在某些情況下你可能會想替換保存在 SecurityContext 中的 Authentication。這可以通過 RunAsManager 來實現(xiàn)的。在 AbstractSecurityInterceptor 的 beforeInvocation()方法體中,在AccessDecisionManager鑒權(quán)成功后,將通過RunAsManager在現(xiàn)有 Authentication 基礎(chǔ)上構(gòu)建一個新的 Authentication,如果新的 Authentication 不為空則將產(chǎn)生一個新的 SecurityContext,并把新產(chǎn)生的 Authentication 存放在其中。這樣在請求受保護(hù)資源時從SecurityContext中獲取到的 Authentication 就是新產(chǎn)生的 Authentication。待請求完成后會在 finallyInvocation()中將原來的SecurityContext 重新設(shè)置給 SecurityContextHolder。AbstractSecurityInterceptor 默認(rèn)持有的是一個對RunAsManager進(jìn)行空實現(xiàn)的NullRunAsManager。此外,Spring Security 對RunAsManager有一個還有一個非空實現(xiàn)類RunAsManagerImpl,其在構(gòu)造新的Authentication時是這樣的邏輯:如果受保護(hù)對象對應(yīng)的 ConfigAttribute 中擁有以“RUN_AS_”開頭的配置屬性,則在該屬性前加上 “ROLE_”,然后再把它作為一個 GrantedAuthority 賦給將要創(chuàng)建的Authentication(如 ConfigAttribute 中擁有一個“RUN_AS_ADMIN”的屬性,則將構(gòu)建一個 “ROLE_RUN_AS_ADMIN” 的 GrantedAuthority),最后再利用原 Authentication 的 principal、權(quán)限等信息構(gòu)建一個新的 Authentication 進(jìn)行返回;如果不存在任何以 “RUN_AS_” 開頭的 ConfigAttribute,則直接返回 null。RunAsManagerImpl 構(gòu)建新的 Authentication 的核心代碼如下所示。

    public Authentication buildRunAs(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
        List<GrantedAuthority> newAuthorities = new ArrayList<GrantedAuthority>();
        for (ConfigAttribute attribute : attributes) {
            if (this.supports(attribute)) {
                GrantedAuthority extraAuthority = new SimpleGrantedAuthority(getRolePrefix() + attribute.getAttribute());
                newAuthorities.add(extraAuthority);
            }
        }
        if (newAuthorities.size() == 0) {
            returnnull;
        }
        // Add existing authorities
        newAuthorities.addAll(authentication.getAuthorities());
        returnnew RunAsUserToken(this.key, authentication.getPrincipal(), authentication.getCredentials(),
                newAuthorities, authentication.getClass());
    }

AfterInvocationManager

在請求受保護(hù)的對象完成以后,可以通過 afterInvocation() 方法對返回值進(jìn)行修改。AbstractSecurityInterceptor 把對返回值進(jìn)行修改的控制權(quán)交給其所持有的 AfterInvocationManager 了。AfterInvocationManager 可以選擇對返回值進(jìn)行修改、不修改或拋出異常(如:后置權(quán)限鑒定不通過)。

以下是 Spring Security 官方文檔提供的一張關(guān)于 AbstractSecurityInterceptor 相關(guān)關(guān)系的圖。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號