javascript
SpringSecurity鉴权流程分析 附源码注释,xdm,一起来看看吧
紙上得來終覺淺,絕知此事要躬行。
閱讀本文:
如需簡單使用👉:SpringBoot集成SpringSecurity做安全框架、附源碼
你能收獲:🛴
一、前言:
xdm,我還是沒有學(xué)會(huì)寫小故事😭,我只可以在這里請(qǐng)你們喝可樂🥤,請(qǐng) xdm 賞個(gè)一鍵三連😁。
xdm,不知道你們在使用SpringSecurity安全框架的時(shí)候,有沒有想過 debug 一步一步看它是如何實(shí)現(xiàn)判斷是否可以訪問的?
如下:
@PreAuthorize("hasRole('ROLE_ADMIN')") @RequestMapping("/role/admin1") String admin() {return "role: ROLE_ADMIN"; }為什么我們寫上這個(gè)注解可以了呢?如何進(jìn)行判斷的呢?
前面寫過一次👨?💻 SpringSecurity 登錄流程分析,寫那篇文章是為了寫👩?💻 SpringSecurity 實(shí)現(xiàn)多種登錄方式做鋪墊。
那么這次寫這個(gè)文章的原因呢?
在掘金看到了掘友的 和耳朵 寫的 SpringSecurity 動(dòng)態(tài)鑒權(quán)流程分析,才發(fā)覺用注解其實(shí)也不是非常好的事情,直接固定在項(xiàng)目,無法做到動(dòng)態(tài)的更改,是個(gè)要不得的事情(捂臉),之前只考慮到這么寫蠻好的,看完文章才恍然大悟。這兩天也準(zhǔn)備實(shí)現(xiàn)一下Security的動(dòng)態(tài)鑒權(quán)的小demo。
xdm,一定要記得,紙上得來終覺淺,絕知此事要躬行,尤其是一路 debug 的文章,親身踩坑。
對(duì)于一門技術(shù),會(huì)使用是說明我們對(duì)它已經(jīng)有了一個(gè)簡單了解,把脈絡(luò)、細(xì)節(jié)都掌握清楚,我們才能更好的使用。
接下來就讓👨?🏫來帶大家一起看看吧。
二、流程圖:
下圖是在百度找的一張關(guān)于 Security 原理圖
我接下來畫的流程圖是基于用戶已經(jīng)登錄的狀態(tài)下的畫的。
整個(gè)認(rèn)證的過程其實(shí)一直在圍繞圖中過濾鏈的綠色部分,而我們今天要說的鑒權(quán)主要是圍繞其橙色部分,也就是圖上標(biāo)的:FilterSecurityInterceptor。
這也就是我流程圖的開始,如下圖:
上圖如有不妥之處,請(qǐng)大家批正,在此鄭重感謝。
關(guān)于上圖的粗略解釋,后文再一一道來:
1、登錄后,用戶訪問一個(gè)需要權(quán)限的接口,經(jīng)過一連串過濾器,到達(dá) FilterSecurityInterceptor, FilterSecurityInterceptor 的invoke()方法執(zhí)行具體攔截行為,具體是 beforeInvocation、finallyInvocation、afterInvocation 這三個(gè)方法,這三個(gè)方法是定義在父類 AbstractSecurityInterceptor 中。
2、調(diào)用 AbstractSecurityInterceptor 的 beforeInvocation 方法。AbstractSecurityInterceptor將確保安全攔截器的正確啟動(dòng)配置。它還將實(shí)現(xiàn)對(duì)安全對(duì)象調(diào)用的正確處理,即:
3、經(jīng)過千辛萬苦后,到達(dá)MethodSecurityInterceptor,由它再次重新調(diào)用起 AbstractSecurityInterceptor.beforeInvocation(mi) 方法,來進(jìn)行權(quán)限的驗(yàn)證
- 鑒權(quán)的時(shí)候,投票者會(huì)換成 PreInvocationAuthorizationAdviceVoter
進(jìn)入正題前先放張圖片緩一緩:
當(dāng)烏云和白云相遇時(shí),而我與你卻已經(jīng)分離。
👨?💻
三、前半部分
前半部分作用是在檢測用戶的狀態(tài),并非就是執(zhí)行鑒權(quán),不過兩次都十分相近。關(guān)于方法上注解的檢測是在后半部分。
1)入口:FilterSecurityInterceptor
第一步:FilterSecurityInterceptor void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
//過濾器鏈實(shí)際調(diào)用的方法。 簡單地委托給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)
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {if (isApplied(filterInvocation) && this.observeOncePerRequest) {//過濾器已應(yīng)用于此請(qǐng)求,用戶希望我們觀察每個(gè)請(qǐng)求處理一次,因此不要重新進(jìn)行安全檢查filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());return;}// 第一次調(diào)用這個(gè)請(qǐng)求,所以執(zhí)行安全檢查if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}//調(diào)用 beforeInvocation(filterInvocation) 方法 跟著這個(gè)方法往下看InterceptorStatusToken token = super.beforeInvocation(filterInvocation) ;try {//每個(gè)過濾器都有這么一步 filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());}finally {//在安全對(duì)象調(diào)用完成后清理AbstractSecurityInterceptor的工作。//無論安全對(duì)象調(diào)用是否成功返回,都應(yīng)該在安全對(duì)象調(diào)用之后和 afterInvocation 之前調(diào)用此方法(即它應(yīng)該在 finally 塊中完成)。super.finallyInvocation(token);}//當(dāng)調(diào)用afterInvocation(InterceptorStatusToken,Object)時(shí),AbstractSecurityInterceptor不會(huì)采取進(jìn)一步的操作。super.afterInvocation(token, null); }2)進(jìn)入:AbstractSecurityInterceptor
授權(quán)檢查 beforeInvocation() 方法
第二步:super.beforeInvocation(filterInvocation); 一些打印信息被精簡了,太長不適合閱讀
protected InterceptorStatusToken beforeInvocation(Object object) {//檢查操作Assert.notNull(object, "Object was null");if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {//....}//這里獲取的信息看下圖示1 ://object 就是調(diào)用處傳過來的參數(shù) FilterInvocation filterInvocation,它本身其實(shí)就是 HttpServletRequest 和 HttpServletResponse 的增強(qiáng)//object :filter invocation [GET /role/admin1] "//然后我們獲取到的就是受保護(hù)調(diào)用的列表 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (CollectionUtils.isEmpty(attributes)) {//...return null; // no further work post-invocation}//在 SecurityContext 中未找到身份驗(yàn)證對(duì)象,會(huì)發(fā)事件拋異常if (SecurityContextHolder.getContext().getAuthentication() == null) {credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"), object, attributes);}//在這里拿到了 Authentication 對(duì)象登錄的信息 ,后文會(huì)簡單說是如何拿到的Authentication authenticated = authenticateIfRequired();if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));}// Attempt authorization : 嘗試授權(quán) 這步本文重點(diǎn),用我的話來說,這就是鑒權(quán)的入口 重點(diǎn)關(guān)注,下文繼續(xù)attemptAuthorization(object, attributes, authenticated);//...// Attempt to run as a different userAuthentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);if (runAs != null) {//...}// 無后續(xù)動(dòng)作return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);}關(guān)于 Collection< ConfigAttribute > attributes = this.obtainSecurityMetadataSource().getAttributes(object);這段代碼。
第一次訪問這里的時(shí)候,FilterSecurityInterceptor是從 SecurityMetadataSource 的子類 DefaultFilterInvocationSecurityMetadataSource獲取到當(dāng)前的是這樣的數(shù)據(jù)。它和我們第二次來執(zhí)行這里有很大的區(qū)別。這里的表達(dá)式是 authenticated,翻譯過來就是認(rèn)證過的。
在后文會(huì)進(jìn)行比較的。
我們接著往下看:Authentication authenticateIfRequired() 獲取身份信息
//如果Authentication.isAuthenticated()返回 false 或?qū)傩詀lwaysReauthenticate已設(shè)置為 true, //則檢查當(dāng)前的身份驗(yàn)證令牌并將其傳遞給 AuthenticationManager進(jìn)行身份驗(yàn)證 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)嘗試授權(quán): attemptAuthorization()
第三步:嘗試授權(quán): attemptAuthorization()
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,Authentication authenticated) {try {//接著套娃 我們?nèi)タ?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 其實(shí)是個(gè)接口。我們一起看看它的源碼
public interface AccessDecisionManager {/** 為傳遞的參數(shù)解析訪問控制決策。 參數(shù): 身份驗(yàn)證 - 調(diào)用方法的調(diào)用者(非空) object – 被調(diào)用的安全對(duì)象 configAttributes – 與被調(diào)用的安全對(duì)象關(guān)聯(lián)的配置屬性*/void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)throws AccessDeniedException, InsufficientAuthenticationException;// 下面這兩個(gè)方法主要起輔助作用的。大都執(zhí)行檢查操作boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);}我們先看看這個(gè)接口結(jié)構(gòu),之后再看它的實(shí)現(xiàn)類內(nèi)部鑒權(quán)機(jī)制是如何執(zhí)行的,需要獲取那些信息,又是如何判斷它是否可以通過的。
我們可以看到這個(gè) AccessDecisionManager 接口,接口下有一個(gè)抽象類,然后再有了三個(gè)實(shí)現(xiàn)類。
他們分別代表不同的機(jī)制。
一起看看默認(rèn)用的 AffirmativeBased:
public class AffirmativeBased extends AbstractAccessDecisionManager {public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {super(decisionVoters);}/**這個(gè)具體的實(shí)現(xiàn)只是輪詢所有配置的AccessDecisionVoter并在任何AccessDecisionVoter投贊成票時(shí)授予訪問權(quán)限。 僅當(dāng)存在拒絕投票且沒有贊成票時(shí)才拒絕訪問。 如果每個(gè)AccessDecisionVoter放棄投票,則決策將基于isAllowIfAllAbstainDecisions()屬性(默認(rèn)為 false)。*/@Override@SuppressWarnings({ "rawtypes", "unchecked" })public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> 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 abstainedcheckAllowIfAllAbstainDecisions();} }到這里又會(huì)牽扯到 AccessDecisionVoter 出來,也就是能夠投票的選民們。
AccessDecisionVoter 投票觀眾接口
我們先一起來看它的源碼,再看看它的實(shí)現(xiàn)類:
//表示一個(gè)類負(fù)責(zé)對(duì)授權(quán)決定進(jìn)行投票。 //投票的協(xié)調(diào)(即輪詢AccessDecisionVoter ,統(tǒng)計(jì)他們的響應(yīng),并做出最終授權(quán)決定)由AccessDecisionManager執(zhí)行。 public interface AccessDecisionVoter<S> {int ACCESS_GRANTED = 1;int ACCESS_ABSTAIN = 0;int ACCESS_DENIED = -1;//這兩個(gè)用來執(zhí)行check操作,判斷參數(shù)是否合法等等boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);/** 指示是否授予訪問權(quán)限。 決定必須是肯定的 ( ACCESS_GRANTED )、否定的 ( ACCESS_DENIED ) 或者 AccessDecisionVoter可以棄權(quán) ( ACCESS_ABSTAIN ) 投票。 在任何情況下,實(shí)現(xiàn)類都不應(yīng)返回任何其他值。 如果需要對(duì)結(jié)果進(jìn)行加權(quán),則應(yīng)改為在自定義AccessDecisionManager處理。 除非AccessDecisionVoter由于傳遞的方法調(diào)用或配置屬性參數(shù)而專門用于對(duì)訪問控制決策進(jìn)行投票,否則它必須返回ACCESS_ABSTAIN 。 這可以防止協(xié)調(diào)AccessDecisionManager計(jì)算來自那些AccessDecisionVoter的選票,而這些AccessDecisionVoter對(duì)訪問控制決策沒有合法利益。 雖然安全對(duì)象(例如MethodInvocation )作為參數(shù)傳遞以最大限度地提高訪問控制決策的靈活性,但實(shí)現(xiàn)類不應(yīng)修改它或?qū)е滤硎镜恼{(diào)用發(fā)生(例如,通過調(diào)用MethodInvocation.proceed() ) .*/int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes); }我們看看它的結(jié)構(gòu):
AffirmativeBased默認(rèn)傳入的構(gòu)造器只有一個(gè) WebExpressionVoter,這個(gè)構(gòu)造器會(huì)根據(jù)你在配置文件中的配置進(jìn)行邏輯處理得出投票結(jié)果。
所以我們在執(zhí)行第一次循環(huán)時(shí),也是在這里處理的。
public class WebExpressionVoter implements AccessDecisionVoter<FilterInvocation> {private SecurityExpressionHandler<FilterInvocation> expressionHandler = new DefaultWebSecurityExpressionHandler();@Overridepublic int vote(Authentication authentication, FilterInvocation filterInvocation,Collection<ConfigAttribute> attributes) {//...執(zhí)行的一些檢查//WebExpressionConfigAttribute webExpressionConfigAttribute = findConfigAttribute(attributes);if (webExpressionConfigAttribute == null) {return ACCESS_ABSTAIN;}//允許對(duì)EvaluationContext進(jìn)行后處理。 實(shí)現(xiàn)可能會(huì)返回一個(gè)新的EvaluationContext實(shí)例或修改傳入的EvaluationContext 。EvaluationContext ctx = webExpressionConfigAttribute.postProcess(//調(diào)用內(nèi)部模板方法來創(chuàng)建StandardEvaluationContext和SecurityExpressionRoot對(duì)象。this.expressionHandler.createEvaluationContext(authentication, filterInvocation), filterInvocation);//針對(duì)指定的根對(duì)象評(píng)估默認(rèn)上下文中的表達(dá)式。 如果評(píng)估結(jié)果與預(yù)期結(jié)果類型不匹配(并且無法轉(zhuǎn)換為),則將返回異常。boolean granted = ExpressionUtils.evaluateAsBoolean(webExpressionConfigAttribute.getAuthorizeExpression(), ctx);// 投贊同票,返回if (granted) {return ACCESS_GRANTED;}return ACCESS_DENIED;}//循環(huán)判斷private WebExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {for (ConfigAttribute attribute : attributes) {if (attribute instanceof WebExpressionConfigAttribute) {return (WebExpressionConfigAttribute) attribute;}}return null;}//... }在這里的數(shù)據(jù)也是如此,和我們上文就互相對(duì)應(yīng)上了。
4)返回過程
4.1、先返回至AffirmativeBased.decide()方法處,投票通過,繼續(xù) retrun
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 方法調(diào)用處,這里是無返回值,直接回到 beforeInvocation 方法中。
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes,Authentication authenticated) {try {this.accessDecisionManager.decide(authenticated, object, attributes);} }4.3、再返回至beforeInvocation 方法中,
protected InterceptorStatusToken beforeInvocation(Object object) {// 返回到這里,我們再順著往下看,看如何執(zhí)行 attemptAuthorization(object, attributes, authenticated);// Attempt to run as a different user :嘗試以其他用戶身份運(yùn)行Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);if (runAs != null) {SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);// 需要恢復(fù)到 token.Authenticated 調(diào)用后 true的意思是:如果能以其他用戶運(yùn)行 就執(zhí)行刷新return new InterceptorStatusToken(origCtx, true, attributes, object);}return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); }4.4、回到了我們夢開始的地方了:FilterSecurityInterceptor.invoke() 方法
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {if (isApplied(filterInvocation) && this.observeOncePerRequest) {// 過濾器已應(yīng)用于此請(qǐng)求,用戶希望我們觀察每個(gè)請(qǐng)求處理一次,因此不要重新進(jìn)行安全檢查filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());return;}// 第一次調(diào)用這個(gè)請(qǐng)求,所以執(zhí)行安全檢查if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);}//返回至此處 //InterceptorStatusToken類上的doc注釋說://AbstractSecurityInterceptor子類接收的返回對(duì)象。//這個(gè)類反映了安全攔截的狀態(tài),以便最終調(diào)用AbstractSecurityInterceptor.afterInvocation(InterceptorStatusToken, Object)可以正確整理。InterceptorStatusToken token = super.beforeInvocation(filterInvocation);try {//每個(gè)過濾器的必備代碼filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());}finally {super.finallyInvocation(token);}super.afterInvocation(token, null);}四、后半部分
對(duì)方法注解的鑒權(quán),是真的一步一步看它如何執(zhí)行的,一直扒,真的是歷經(jīng)千辛萬苦。
默認(rèn)大家都能看的懂這個(gè)圖了,我們直接轉(zhuǎn)到 MethodSecurityInterceptor 里來看看它做了什么吧
4.1、入口:MethodSecurityInterceptor
//提供對(duì)基于 AOP 聯(lián)盟的方法調(diào)用的安全攔截。 //此安全攔截器所需的SecurityMetadataSource是MethodSecurityMetadataSource類型。 這與基于 AspectJ 的安全攔截器 ( AspectJSecurityInterceptor ) 共享,因?yàn)閮烧叨寂c Java Method 。 public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {private MethodSecurityMetadataSource securityMetadataSource;//此方法應(yīng)用于對(duì)MethodInvocation強(qiáng)制實(shí)施安全性。@Overridepublic Object invoke(MethodInvocation mi) throws Throwable {//beforeInvocation 這個(gè)有沒有似曾相識(shí) ,莫錯(cuò)哈 就是我們之前在 FilterSecurityInterceptor 看到的那個(gè) //需要注意到的是 之前我們傳的參是一個(gè) FilterInvocation ,這里則是一個(gè) MethodInvocation 。InterceptorStatusToken token = super.beforeInvocation(mi);Object result;try {result = mi.proceed();}finally {super.finallyInvocation(token);}return super.afterInvocation(token, result);}//... }MethodInvocation :doc注釋是"方法調(diào)用的描述,在方法調(diào)用時(shí)提供給攔截器。方法調(diào)用是一個(gè)連接點(diǎn),可以被方法攔截器攔截".
4.2、進(jìn)入 AbstractSecurityInterceptor
授權(quán)檢查 beforeInvocation() 方法
另外在這里debug獲取到的值也是不一樣的,這點(diǎn)上文我剛剛也說過了。
獲取資源訪問策略:FilterSecurityInterceptor 會(huì)從 SecurityMetadataSource 的子類 DefaultFilterInvocationSecurityMetadataSource 獲取要訪問當(dāng)前資源所需要的權(quán)限 Collection< ConfigAttribute >。 SecurityMetadataSource 其實(shí)就是讀取訪問策略的抽象,而讀取的內(nèi)容,其實(shí)就是我們配置的訪問規(guī)則, 讀取訪問策略如:
protected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/r/r1").hasAuthority("r1").antMatchers("/r/r2").hasAuthority("r2").... }中間的過程同上半部分差不多,就不多說了。我們直接看 AffirmativeBased 情況如何。
4.3、轉(zhuǎn)戰(zhàn):AffirmativeBasedl;
attemptAuthorization(object, attributes, authenticated); this.accessDecisionManager.decide(authenticated, object, attributes);接著往下,到此處就同之前稍有不同了,我們之前用到的是 WebExpressionVoter,在這里我們使用的是: PreInvocationAuthorizationAdviceVoter
我們接著進(jìn)入:PreInvocationAuthorizationAdviceVoter,它的類上的doc注釋如下:
Voter 使用從 @PreFilter 和 @PreAuthorize 注釋生成的 PreInvocationAuthorizationAdvice 實(shí)現(xiàn)來執(zhí)行操作。
在實(shí)踐中,如果使用這些注解,它們通常會(huì)包含所有必要的訪問控制邏輯,因此基于投票者的系統(tǒng)并不是真正必要的,包含相同邏輯的單個(gè)AccessDecisionManager就足夠了。 然而,這個(gè)類很容易與 Spring Security 使用的傳統(tǒng)的基于投票者的AccessDecisionManager實(shí)現(xiàn)相適應(yīng)。
我們可以很容易的看出,這個(gè)就是處理方法上注解的那個(gè)類。接著看下它的源碼。
public class PreInvocationAuthorizationAdviceVoter implements AccessDecisionVoter<MethodInvocation> {private final PreInvocationAuthorizationAdvice preAdvice;public PreInvocationAuthorizationAdviceVoter(PreInvocationAuthorizationAdvice pre) {this.preAdvice = pre;}//...一些檢查方法@Overridepublic int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {// 查找 prefilter 和 preauth(或組合)屬性,如果兩者都為 null,則棄權(quán)使用它們調(diào)用建議PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);if (preAttr == null) {// 沒有基于表達(dá)式的元數(shù)據(jù),所以棄權(quán)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方法的默認(rèn)實(shí)現(xiàn):
before方法的說明是:應(yīng)該執(zhí)行的“before”建議以執(zhí)行任何必要的過濾并決定方法調(diào)用是否被授權(quán)。
我們先說說它的參數(shù):(Authentication authentication,MethodInvocation mi,PreInvocationAttribute attr),第一個(gè)就是當(dāng)前登錄的用戶,二就是要執(zhí)行的方法,三就是方法上的注解信息。
我們可以很簡單的看出這段代碼的含義,就是在比較已經(jīng)登錄的用戶,是否擁有這個(gè)方法上所需要的權(quán)限。
另外簡單說明一下:
createEvaluationContext 的dco注釋:提供評(píng)估上下文,在其中評(píng)估調(diào)用類型的安全表達(dá)式(即 SpEL 表達(dá)式)。我個(gè)人對(duì)這塊沒有特別深入過,沒法說清楚,大家可以查一查。
另外我們看一下debug的詳細(xì)信息,大家應(yīng)該就差不多能懂啦。
接下來就是一步一步返回啦
最后就是:
這里的 result 就是方法執(zhí)行的返回結(jié)果。緊接著就是一步一步返回過濾器鏈啦。
對(duì)于這里 proceed方法就不再深入了。這個(gè)點(diǎn)拉出來說,怕是直接可以寫上一篇完整的文章啦。
內(nèi)部很多動(dòng)態(tài)代理啊、反射啊這些相關(guān)的,一層套一層的,不是咱研究重點(diǎn)。溜啦溜啦。
五、小結(jié)
這張圖是在百度上搜到的,大致流程其實(shí)就是如此。
其實(shí)內(nèi)部還有很多很多值得推敲的東西,不是在這一篇簡單的文章中能夠?qū)懗鰜淼摹?/p>
六、自我感受
還記得我第一次說要看源碼是在準(zhǔn)備研究 Mybatis 的時(shí)候,那時(shí)候上頭看了大概幾天吧,看著看著就看不下去了,找不到一個(gè)合適的方法,什么都想看,沒有一個(gè)非常具體的目標(biāo),導(dǎo)致連續(xù)受挫,結(jié)果就是不了了之了。
第二次真正意義看源碼就是看 Security 。原因是當(dāng)時(shí)在寫項(xiàng)目的時(shí),我的前端小伙伴說,現(xiàn)在大部分網(wǎng)站都有多種登錄方式,你能實(shí)現(xiàn)不?
男人肯定是不能說不行,然后我就一口答應(yīng)下來了。結(jié)果就是瘋狂百度、google,到處看博客。互聯(lián)網(wǎng)這么龐大,當(dāng)然也有找到非常多的例子,也有源碼解析。但是找到的文章,要么只貼出了核心代碼,要么就是不合適(龐大,難以抽取),總之一句話沒法運(yùn)行。就很煩操。
不過文章中都提到了要理解 Security 的登錄過程,然后進(jìn)行仿寫,俗稱抄作業(yè)。最后,真就是一步一步 debug 去看 Security 的登錄過程,寫出了 第一篇 Security登錄認(rèn)證流程分析,緊接著又去用 SpringSecurity實(shí)現(xiàn)多種登錄方式,如郵件驗(yàn)證碼、電話號(hào)碼登錄。這次即是機(jī)緣巧合,也是心有所念,耗費(fèi)不少時(shí)間寫出了這篇文章。感覺還是非常不錯(cuò)的。
希望大家能夠喜歡,如果 xdm 對(duì)此也感興趣,希望大家在有時(shí)間的情況,debug 幾次,記憶會(huì)深刻很多。并竟 紙上得來終覺淺,絕知此事要躬行。
相關(guān)文章:
今天的文章就到這里了。
你好,我是博主寧在春:主頁
如若在文章中遇到疑惑,請(qǐng)留言或私信,或者加主頁聯(lián)系方式,都會(huì)盡快回復(fù)。
如若發(fā)現(xiàn)文章中存在問題,望你能夠指正,不勝感謝。
如果覺得對(duì)你有所幫助的話,請(qǐng)點(diǎn)個(gè)贊再走吧!
總結(jié)
以上是生活随笔為你收集整理的SpringSecurity鉴权流程分析 附源码注释,xdm,一起来看看吧的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: VsCode 配置 C/C++ 开发环境
- 下一篇: 获取Class对象的三种方式