針對 SpringSecurity 鑑權流程做了一個詳細分析,讓你明白它是如何執行的!

語言: CN / TW / HK

highlight: a11y-dark theme: v-green


本文已參與「掘力星計劃」,贏取創作大禮包,挑戰創作激勵金。

紙上得來終覺淺,絕知此事要躬行。

閱讀本文:

如需簡單使用👉:SpringBoot整合SpringSecurity做安全框架、附原始碼

你能收穫:🛴

  1. 你能大致明白 SpringSecurity 鑑權流程。
  2. 能夠 Debug 一步一步能夠畫出 SpringSecurity 鑑權流程圖。
  3. 對於 SpringSecurity 框架會有更深一步的理解,能夠在使用時做到更高程度的定製化。
  4. 以及對 SpringSecurity 更深一步的思考

一、前言:

xdm,我還是沒有學會寫小故事😭,我只可以在這裡請你們喝可樂🥤,請 xdm 賞個一鍵三連😁。

image-20211015195832816

xdm,不知道你們在使用SpringSecurity安全框架的時候,有沒有想過 debug 一步一步看它是如何實現判斷是否可以訪問的?

如下:

java @PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping("/role/admin1") String admin() { return "role: ROLE_ADMIN"; }

為什麼我們寫上這個註解可以了呢?如何進行判斷的呢?

前面寫過一次👨‍💻 SpringSecurity 登入流程分析,寫那篇文章是為了寫👩‍💻 SpringSecurity 實現多種登入方式做鋪墊。

那麼這次寫這個文章的原因呢?

在掘金看到了掘友的 和耳朵 寫的 SpringSecurity 動態鑑權流程分析,才發覺用註解其實也不是個非常好的事情,直接固定在專案,無法做到動態的更改,是個要不得的事情(捂臉),之前只考慮到這麼寫蠻好的,看完文章才恍然大悟。這兩天也準備實現一下Security的動態鑑權的小demo。

xdm,一定要記得,紙上得來終覺淺,絕知此事要躬行,尤其是一路 debug 的文章,親身踩坑。

對於一門技術,會使用是說明我們對它已經有了一個簡單瞭解,把脈絡、細節都掌握清楚,我們才能更好的使用。

接下來就讓👨‍🏫來帶大家一起看看吧。

二、流程圖:

下圖是在百度找的一張關於 Security 原理圖

image-20211015202632522

我接下來畫的流程圖是基於使用者已經登入的狀態下的畫的。

整個認證的過程其實一直在圍繞圖中過濾鏈的綠色部分,而我們今天要說的鑑權主要是圍繞其橙色部分,也就是圖上標的:FilterSecurityInterceptor

這也就是我流程圖的開始,如下圖:

image-20211016152246436

上圖如有不妥之處,請大家批正,在此鄭重感謝。

關於上圖的粗略解釋,後文再一一道來:

1、登入後,使用者訪問一個需要許可權的介面,經過一連串過濾器,到達 FilterSecurityInterceptor, FilterSecurityInterceptor 的invoke()方法執行具體攔截行為,具體是 beforeInvocation、finallyInvocation、afterInvocation 這三個方法,這三個方法是定義在父類 AbstractSecurityInterceptor 中。

2、呼叫 AbstractSecurityInterceptor 的 beforeInvocation 方法。AbstractSecurityInterceptor將確保安全攔截器的正確啟動配置。它還將實現對安全物件呼叫的正確處理,即:

  1. 獲取訪問當前資源所需要的許可權SecurityMetadataSource..getAttributes(object);返回個 Collection< ConfigAttribute > attributes
  2. 從SecurityContextHolder獲取Authentication物件。 `Authentication authenticated = authenticateIfRequired();
  3. 嘗試授權 attemptAuthorization(object, attributes, authenticated); 呼叫 AccessDecisionManager 介面 decide 方法,執行鑑權,鑑權不成功,會直接拋異常。
  4. 返回一個InterceptorStatusToken。

3、經過千辛萬苦後,到達MethodSecurityInterceptor,由它再次重新呼叫起 AbstractSecurityInterceptor.beforeInvocation(mi) 方法,來進行許可權的驗證

  • 鑑權的時候,投票者會換成 PreInvocationAuthorizationAdviceVoter

進入正題前先放張圖片緩一緩:

123456.jpg

當烏雲和白雲相遇


👨‍💻

image-20211015201424942

三、前半部分

前半部分作用是在檢測使用者的狀態,並非就是執行鑑權,不過兩次都十分相近。關於方法上註解的檢測是在後半部分。

1)入口:FilterSecurityInterceptor

第一步:FilterSecurityInterceptor void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

java //過濾器鏈實際呼叫的方法。 簡單地委託給invoke(FilterInvocation)方法。 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { invoke(new FilterInvocation(request, response, chain)); }

接著看 void invoke(FilterInvocation filterInvocation)

```java public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { if (isApplied(filterInvocation) && this.observeOncePerRequest) { //過濾器已應用於此請求,使用者希望我們觀察每個請求處理一次,因此不要重新進行安全檢查 filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); return; } // 第一次呼叫這個請求,所以執行安全檢查 if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); }

//呼叫 beforeInvocation(filterInvocation) 方法 跟著這個方法往下看
InterceptorStatusToken token = super.beforeInvocation(filterInvocation) ;
try {
    //每個過濾器都有這麼一步 
    filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
}
finally {
    //在安全物件呼叫完成後清理AbstractSecurityInterceptor的工作。
    //無論安全物件呼叫是否成功返回,都應該在安全物件呼叫之後和 afterInvocation 之前呼叫此方法(即它應該在 finally 塊中完成)。
    super.finallyInvocation(token);
}
//當呼叫afterInvocation(InterceptorStatusToken,Object)時,AbstractSecurityInterceptor不會採取進一步的操作。
super.afterInvocation(token, null);

} ```

2)進入:AbstractSecurityInterceptor

授權檢查 beforeInvocation() 方法

第二步:super.beforeInvocation(filterInvocation); 一些列印資訊被精簡了,太長不適合閱讀

```java protected InterceptorStatusToken beforeInvocation(Object object) { //檢查操作 Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { //.... } //這裡獲取的資訊看下圖示1 : //object 就是呼叫處傳過來的引數 FilterInvocation filterInvocation,它本身其實就是 HttpServletRequest 和 HttpServletResponse 的增強 //object :filter invocation [GET /role/admin1] " //然後我們獲取到的就是受保護呼叫的列表
Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object); if (CollectionUtils.isEmpty(attributes)) { //... return null; // no further work post-invocation } //在 SecurityContext 中未找到身份驗證物件,會發事件拋異常 if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); }

    //在這裡拿到了  Authentication 物件登入的資訊 ,後文會簡單說是如何拿到的
    Authentication authenticated = authenticateIfRequired();
    if (this.logger.isTraceEnabled()) {
        this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
    }
    // Attempt authorization : 嘗試授權 這步本文重點,用我的話來說,這就是鑑權的入口  重點關注,下文繼續
    attemptAuthorization(object, attributes, authenticated);
    //...
    // Attempt to run as a different user
    Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
    if (runAs != null) {
        //...
    }
    // 無後續動作
    return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);

}

```

關於 Collection< ConfigAttribute > attributes = this.obtainSecurityMetadataSource().getAttributes(object);這段程式碼。

第一次訪問這裡的時候,FilterSecurityInterceptor是從 SecurityMetadataSource 的子類 DefaultFilterInvocationSecurityMetadataSource獲取到當前的是這樣的資料。它和我們第二次來執行這裡有很大的區別。這裡的表示式是 authenticated,翻譯過來就是認證過的。

image-20211016160929332

在後文會進行比較的。

我們接著往下看:Authentication authenticateIfRequired() 獲取身份資訊

java //如果Authentication.isAuthenticated()返回 false 或屬性alwaysReauthenticate已設定為 true, //則檢查當前的身份驗證令牌並將其傳遞給 AuthenticationManager進行身份驗證 private Authentication authenticateIfRequired() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication.isAuthenticated() && !this.alwaysReauthenticate) { return authentication; } authentication = this.authenticationManager.authenticate(authentication); SecurityContextHolder.getContext().setAuthentication(authentication); return authentication; }

3)嘗試授權: attemptAuthorization()

第三步:嘗試授權: attemptAuthorization()

java private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) { try { //接著套娃 我們去看 AccessDecisionManager 下的 decide() 方法 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException ex) { if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager)); } else if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); } publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex)); throw ex; } }

AccessDecisionManager 決策器說明:

this.accessDecisionManager 其實是個介面。我們一起看看它的原始碼

```java public interface AccessDecisionManager {

/**

為傳遞的引數解析訪問控制決策。 引數: 身份驗證 - 呼叫方法的呼叫者(非空) object – 被呼叫的安全物件 configAttributes – 與被呼叫的安全物件關聯的配置屬性 */ void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException, InsufficientAuthenticationException;

// 下面這兩個方法主要起輔助作用的。大都執行檢查操作
boolean supports(ConfigAttribute attribute);

boolean supports(Class<?> clazz);

} ```

我們先看看這個介面結構,之後再看它的實現類內部鑑權機制是如何執行的,需要獲取那些資訊,又是如何判斷它是否可以通過的。

我們可以看到這個 AccessDecisionManager 介面,介面下有一個抽象類,然後再有了三個實現類。

image-20211015132038199

他們分別代表不同的機制。

  1. AffirmativeBased:如果任何AccessDecisionVoter返回肯定響應,則授予訪問許可權。即有一票同意,就可以通過,預設是它。
  2. ConsensusBased:少數服從於多數。多數票同意通過,即可以通過。如民主選舉制一樣。
  3. UnanimousBased:要求所有選民棄權或授予訪問許可權。簡稱一票反對。只要有一票反對就不能通過。

一起看看預設用的 AffirmativeBased:

```java public class AffirmativeBased extends AbstractAccessDecisionManager {

public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
    super(decisionVoters);
}

/**
這個具體的實現只是輪詢所有配置的AccessDecisionVoter並在任何AccessDecisionVoter投贊成票時授予訪問許可權。 僅當存在拒絕投票且沒有贊成票時才拒絕訪問。

如果每個AccessDecisionVoter放棄投票,則決策將基於isAllowIfAllAbstainDecisions()屬性(預設為 false)。 */ @Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException { int deny = 0; for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); } }

```

到這裡又會牽扯到 AccessDecisionVoter 出來,也就是能夠投票的選民們。

AccessDecisionVoter 投票觀眾介面

我們先一起來看它的原始碼,再看看它的實現類:

```java //表示一個類負責對授權決定進行投票。 //投票的協調(即輪詢AccessDecisionVoter ,統計他們的響應,並做出最終授權決定)由AccessDecisionManager執行。 public interface AccessDecisionVoter {

int ACCESS_GRANTED = 1;

int ACCESS_ABSTAIN = 0;

int ACCESS_DENIED = -1;

//這兩個用來執行check操作,判斷引數是否合法等等
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);

/**

指示是否授予訪問許可權。 決定必須是肯定的 ( ACCESS_GRANTED )、否定的 ( ACCESS_DENIED ) 或者 AccessDecisionVoter可以棄權 ( ACCESS_ABSTAIN ) 投票。 在任何情況下,實現類都不應返回任何其他值。 如果需要對結果進行加權,則應改為在自定義AccessDecisionManager處理。 除非AccessDecisionVoter由於傳遞的方法呼叫或配置屬性引數而專門用於對訪問控制決策進行投票,否則它必須返回ACCESS_ABSTAIN 。 這可以防止協調AccessDecisionManager計算來自那些AccessDecisionVoter的選票,而這些AccessDecisionVoter對訪問控制決策沒有合法利益。 雖然安全物件(例如MethodInvocation )作為引數傳遞以最大限度地提高訪問控制決策的靈活性,但實現類不應修改它或導致所表示的呼叫發生(例如,通過呼叫MethodInvocation.proceed() ) . */ int vote(Authentication authentication, S object, Collection attributes); } ```

我們看看它的結構:

image-20211015133632166

  1. RoleVoter 主要用來判斷當前請求是否具備該介面所需要的角色
  2. RoleHierarchyVoter 是 RoleVoter 的一個子類,在 RoleVoter 角色判斷的基礎上,引入了角色分層管理,也就是角色繼承
  3. WebExpressionVoter 這是一個基於表示式許可權控制的投票器
  4. Jsr250Voter 處理 Jsr-250 許可權註解的投票器,如 @PermitAll@DenyAll 等。
  5. AuthenticatedVoter 用於判斷 ConfigAttribute 上是否擁有 IS_AUTHENTICATED_FULLY、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 三種角色。
  6. AbstractAclVoter 提供編寫域物件 ACL 選項的幫助方法,沒有繫結到任何特定的 ACL 系統。
  7. PreInvocationAuthorizationAdviceVoter 使用 @PreFilter 和 @PreAuthorize 註解處理的許可權,通過 PreInvocationAuthorizationAdvice 來授權。

AffirmativeBased預設傳入的構造器只有一個 WebExpressionVoter,這個構造器會根據你在配置檔案中的配置進行邏輯處理得出投票結果。

所以我們在執行第一次迴圈時,也是在這裡處理的。

```java public class WebExpressionVoter implements AccessDecisionVoter { private SecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();

@Override
public int vote(Authentication authentication, FilterInvocation filterInvocation,
        Collection<ConfigAttribute> attributes) {
    //...執行的一些檢查
    //
    WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);
    if (webExpressionConfigAttribute == null) {
        return ACCESS_ABSTAIN;
    }
    //允許對EvaluationContext進行後處理。 實現可能會返回一個新的EvaluationContext例項或修改傳入的EvaluationContext 。
    EvaluationContext ctx = webExpressionConfigAttribute.postProcess(
        //呼叫內部模板方法來建立StandardEvaluationContext和SecurityExpressionRoot物件。
        this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);
    //針對指定的根物件評估預設上下文中的表示式。 如果評估結果與預期結果型別不匹配(並且無法轉換為),則將返回異常。
    boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);
    // 投贊同票,返回
    if (granted) {
        return ACCESS_GRANTED;
    }
    return ACCESS_DENIED;
}

//迴圈判斷
private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
    for (ConfigAttribute attribute : attributes) {
        if (attribute instanceof WebExpressionConfigAttribute) {
            return (WebExpressionConfigAttribute) attribute;
        }
    }
    return null;
}
//...

} ```

image-20211016163159648

在這裡的資料也是如此,和我們上文就互相對應上了。

4)返回過程

4.1、先返回至AffirmativeBased.decide()方法處,投票通過,繼續 retrun

java for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } }

4.2、返回至 AbstractSecurityInterceptor 方法呼叫處,這裡是無返回值,直接回到 beforeInvocation 方法中。

java private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) { try { this.accessDecisionManager.decide(authenticated, object, attributes); } }

4.3、再返回至beforeInvocation 方法中,

java protected InterceptorStatusToken beforeInvocation(Object object) { // 返回到這裡,我們再順著往下看,看如何執行 attemptAuthorization(object, attributes, authenticated); // Attempt to run as a different user :嘗試以其他使用者身份執行 Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs != null) { SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // 需要恢復到 token.Authenticated 呼叫後 true的意思是:如果能以其他使用者執行 就執行重新整理 return new InterceptorStatusToken(origCtx, true, attributes, object); } return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); }

4.4、回到了我們夢開始的地方了:FilterSecurityInterceptor.invoke() 方法

java public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException { if (isApplied(filterInvocation) && this.observeOncePerRequest) { // 過濾器已應用於此請求,使用者希望我們觀察每個請求處理一次,因此不要重新進行安全檢查 filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); return; } // 第一次呼叫這個請求,所以執行安全檢查 if (filterInvocation.getRequest() != null && this.observeOncePerRequest) { filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE); } //返回至此處 //InterceptorStatusToken類上的doc註釋說: //AbstractSecurityInterceptor子類接收的返回物件。 //這個類反映了安全攔截的狀態,以便最終呼叫AbstractSecurityInterceptor.afterInvocation(InterceptorStatusToken, Object)可以正確整理。 InterceptorStatusToken token = super.beforeInvocation(filterInvocation); try { //每個過濾器的必備程式碼 filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse()); } finally { super.finallyInvocation(token); } super.afterInvocation(token, null); }

四、後半部分

對方法註解的鑑權,是真的一步一步看它如何執行的,一直扒,真的是歷經千辛萬苦。

image-20211016140515085

預設大家都能看的懂這個圖了,我們直接轉到 MethodSecurityInterceptor 裡來看看它做了什麼吧

4.1、入口:MethodSecurityInterceptor

```java //提供對基於 AOP 聯盟的方法呼叫的安全攔截。 //此安全攔截器所需的SecurityMetadataSource是MethodSecurityMetadataSource型別。 這與基於 AspectJ 的安全攔截器 ( AspectJSecurityInterceptor ) 共享,因為兩者都與 Java Method 。 public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {

private MethodSecurityMetadataSource securityMetadataSource;

//此方法應用於對MethodInvocation強制實施安全性。
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
    //beforeInvocation 這個有沒有似曾相識 ,莫錯哈  就是我們之前在 FilterSecurityInterceptor 看到的那個 
    //需要注意到的是 之前我們傳的參是一個  FilterInvocation ,這裡則是一個  MethodInvocation 。
    InterceptorStatusToken token = super.beforeInvocation(mi);
    Object result;
    try {
        result = mi.proceed();
    }
    finally {
        super.finallyInvocation(token);
    }
    return super.afterInvocation(token, result);
}
//...

} ```

MethodInvocation :doc註釋是"方法呼叫的描述,在方法呼叫時提供給攔截器。方法呼叫是一個連線點,可以被方法攔截器攔截".

4.2、進入 AbstractSecurityInterceptor

授權檢查 beforeInvocation() 方法

image-20211016171816409

另外在這裡debug獲取到的值也是不一樣的,這點上文我剛剛也說過了。

獲取資源訪問策略:FilterSecurityInterceptor 會從 SecurityMetadataSource 的子類 DefaultFilterInvocationSecurityMetadataSource 獲取要訪問當前資源所需要的許可權 Collection< ConfigAttribute >。 SecurityMetadataSource 其實就是讀取訪問策略的抽象,而讀取的內容,其實就是我們配置的訪問規則, 讀取訪問策略如:

java protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/r/r1").hasAuthority("r1") .antMatchers("/r/r2").hasAuthority("r2") .... }

image-20211016182852123

中間的過程同上半部分差不多,就不多說了。我們直接看 AffirmativeBased 情況如何。

4.3、轉戰:AffirmativeBasedl;

java attemptAuthorization(object, attributes, authenticated); this.accessDecisionManager.decide(authenticated, object, attributes);

接著往下,到此處就同之前稍有不同了,我們之前用到的是 WebExpressionVoter,在這裡我們使用的是: PreInvocationAuthorizationAdviceVoter

image-20211015160437849

我們接著進入:PreInvocationAuthorizationAdviceVoter,它的類上的doc註釋如下:

Voter 使用從 @PreFilter 和 @PreAuthorize 註釋生成的 PreInvocationAuthorizationAdvice 實現來執行操作。 在實踐中,如果使用這些註解,它們通常會包含所有必要的訪問控制邏輯,因此基於投票者的系統並不是真正必要的,包含相同邏輯的單個AccessDecisionManager就足夠了。 然而,這個類很容易與 Spring Security 使用的傳統的基於投票者的AccessDecisionManager實現相適應。

我們可以很容易的看出,這個就是處理方法上註解的那個類。接著看下它的原始碼。

```java public class PreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter {

private final PreInvocationAuthorizationAdvice preAdvice;

public PreInvocationAuthorizationAdviceVoter(PreInvocationAuthorizationAdvice pre) {
    this.preAdvice = pre;
}
//...一些檢查方法
@Override
public int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {
    // 查詢 prefilter 和 preauth(或組合)屬性,如果兩者都為 null,則棄權使用它們呼叫建議
    PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
    if (preAttr == null) {
        // 沒有基於表示式的元資料,所以棄權
        return ACCESS_ABSTAIN;
    }
    //在這裡又委託給 PreInvocationAuthorizationAdvice介面的before方法來做判斷
    return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;
}

private PreInvocationAttribute findPreInvocationAttribute(Collection<ConfigAttribute> config) {
    for (ConfigAttribute attribute : config) {
        if (attribute instanceof PreInvocationAttribute) {
            return (PreInvocationAttribute) attribute;
        }
    }
    return null;
}

} ```

簡單看一下PreInvocationAuthorizationAdvice介面的before方法的預設實現:

before方法的說明是:應該執行的“before”建議以執行任何必要的過濾並決定方法呼叫是否被授權。

image-20211015161226479

我們先說說它的引數:(Authentication authentication,MethodInvocation mi,PreInvocationAttribute attr),第一個就是當前登入的使用者,二就是要執行的方法,三就是方法上的註解資訊。 我們可以很簡單的看出這段程式碼的含義,就是在比較已經登入的使用者,是否擁有這個方法上所需要的許可權。

另外簡單說明一下:

  1. createEvaluationContext 的dco註釋:提供評估上下文,在其中評估呼叫型別的安全表示式(即 SpEL 表示式)。我個人對這塊沒有特別深入過,沒法說清楚,大家可以查一查。

  2. 另外我們看一下debug的詳細資訊,大家應該就差不多能懂啦。

    image-20211016182251432

接下來就是一步一步返回啦

最後就是:

image-20211015162556081

這裡的 result 就是方法執行的返回結果。緊接著就是一步一步返回過濾器鏈啦。

對於這裡 proceed方法就不再深入了。這個點拉出來說,怕是直接可以寫上一篇完整的文章啦。

內部很多動態代理啊、反射啊這些相關的,一層套一層的,不是咱研究重點。溜啦溜啦。

五、小結

這張圖是在百度上搜到的,大致流程其實就是如此。

20200503214152166

其實內部還有很多很多值得推敲的東西,不是在這一篇簡單的文章中能夠寫出來的。

六、自我感受

還記得我第一次說要看原始碼是在準備研究 Mybatis 的時候,那時候上頭看了大概幾天吧,看著看著就看不下去了,找不到一個合適的方法,什麼都想看,沒有一個非常具體的目標,導致連續受挫,結果就是不了了之了。

第二次真正意義看原始碼就是看 Security 。原因是當時在寫專案的時,我的前端小夥伴說,現在大部分網站都有多種登入方式,你能實現不?

男人肯定是不能說不行,然後我就一口答應下來了。結果就是瘋狂百度、google,到處看部落格。網際網路這麼龐大,當然也有找到非常多的例子,也有原始碼解析。但是找到的文章,要麼只貼出了核心程式碼,要麼就是不合適(龐大,難以抽取),總之一句話沒法執行。就很煩操。

不過文章中都提到了要理解 Security 的登入過程,然後進行仿寫,俗稱抄作業。最後,真就是一步一步 debug 去看 Security 的登入過程,寫出了 第一篇 Security登入認證流程分析,緊接著又去用 SpringSecurity實現多種登入方式,如郵件驗證碼、電話號碼登入。這次即是機緣巧合,也是心有所念,耗費不少時間寫出了這篇文章。感覺還是非常不錯的。

希望大家能夠喜歡,如果 xdm 對此也感興趣,希望大家在有時間的情況,debug 幾次,記憶會深刻很多。並竟 紙上得來終覺淺,絕知此事要躬行

相關文章:

  1. SpringBoot整合SpringSecurity做安全框架
  2. Security的登入流程詳解
  3. Security實現多種登入方式、郵件驗證碼、手機驗證碼登入。
  4. SpringSecurity許可權命名ROLE_問題

今天的文章就到這裡了。

你好,我是博主寧在春主頁

如若在文章中遇到疑惑,請留言或私信,或者加主頁聯絡方式,都會盡快回復。

如若發現文章中存在問題,望你能夠指正,不勝感謝。

如果覺得對你有所幫助的話,請點個贊再走吧!