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

2020-07-14 10:48 更新

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

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

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

Spring Security 的 AOP Advice 思想

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

對(duì)于大部分人而言都比較喜歡對(duì) Service 層的方法調(diào)用進(jìn)行權(quán)限控制,因?yàn)槲覀兊闹饕獦I(yè)務(wù)邏輯都是在 Service 層進(jìn)行實(shí)現(xiàn)的。如果你只是想保護(hù) Service 層的方法,那么使用 Spring AOP 就可以了。如果你需要直接保護(hù)領(lǐng)域?qū)ο?,那么你可以考慮使用 Aspectj。

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

AbstractSecurityInterceptor

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

  1. 先將正在請(qǐng)求調(diào)用的受保護(hù)對(duì)象傳遞給 beforeInvocation()方法進(jìn)行權(quán)限鑒定。
  2. 權(quán)限鑒定失敗就直接拋出異常了。
  3. 鑒定成功將嘗試調(diào)用受保護(hù)對(duì)象,調(diào)用完成后,不管是成功調(diào)用,還是拋出異常,都將執(zhí)行 finallyInvocation()。
  4. 如果在調(diào)用受保護(hù)對(duì)象后沒(méi)有拋出異常,則調(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)的時(shí)候使用的是注入的 AccessDecisionManager 的 decide() 方法進(jìn)行的。如前所述,decide()方法是需要接收一個(gè)受保護(hù)對(duì)象對(duì)應(yīng)的 ConfigAttribute 集合的。一個(gè) ConfigAttribute 可能只是一個(gè)簡(jiǎn)單的角色名稱,具體將視 AccessDecisionManager 的實(shí)現(xiàn)者而定。AbstractSecurityInterceptor 將使用一個(gè) SecurityMetadataSource 對(duì)象來(lái)獲取與受保護(hù)對(duì)象關(guān)聯(lián)的 ConfigAttribute 集合,具體 SecurityMetadataSource 將由子類實(shí)現(xiàn)提供。ConfigAttribute 將通過(guò)注解的形式定義在受保護(hù)的方法上,或者通過(guò) access 屬性定義在受保護(hù)的 URL 上。例如我們常見(jiàn)的 就表示將 ConfigAttribute ROLE_USER 和 ROLE_ADMIN 應(yīng)用在所有的 URL 請(qǐng)求上。對(duì)于默認(rèn)的 AccessDecisionManager 的實(shí)現(xiàn),上述配置意味著用戶所擁有的權(quán)限中只要擁有一個(gè) GrantedAuthority 與這兩個(gè) ConfigAttribute 中的一個(gè)進(jìn)行匹配則允許進(jìn)行訪問(wèn)。當(dāng)然,嚴(yán)格的來(lái)說(shuō) ConfigAttribute 只是一個(gè)簡(jiǎn)單的配置屬性而已,具體的解釋將由 AccessDecisionManager 來(lái)決定。

RunAsManager

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

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

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


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)