- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- Spring Security源碼解析之權限訪(fǎng)問(wèn)控制是如何做到
在實(shí)戰篇《》我們學(xué)習了Spring Security強大的訪(fǎng)問(wèn)控制能力,只需要進(jìn)行寥寥幾行的配置就能做到權限的控制,本篇來(lái)看看它到底是如何做到的。
源碼篇中反復提到,請求進(jìn)來(lái)需要經(jīng)過(guò)的是一堆過(guò)濾器形成的過(guò)濾器鏈,走完過(guò)濾器鏈未拋出異常則可以繼續訪(fǎng)問(wèn)后臺接口資源,而最后一個(gè)過(guò)濾器就是來(lái)判斷請求是否有權限繼續訪(fǎng)問(wèn)后臺資源,如果沒(méi)有則會(huì )將拒絕訪(fǎng)問(wèn)的異常往上向異常過(guò)濾器拋,異常過(guò)濾器會(huì )對異常進(jìn)行翻譯,然后響應給客戶(hù)端。
所以,一般情況下最后一個(gè)過(guò)濾器是做權限訪(fǎng)問(wèn)控制的核心過(guò)濾器FilterSecurityInterceptor
,而倒數第二個(gè)是異常翻譯過(guò)濾器ExceptionTranslationFilter
,將異常進(jìn)行翻譯然后響應給客戶(hù)端。比如我們實(shí)戰項目過(guò)濾器鏈圖解
這個(gè)過(guò)濾器的配置器是 ExpressionUrlAuthorizationConfigurer
,它的父類(lèi) AbstractInterceptUrlConfigurer
中的 configure()
方法創(chuàng )建了這個(gè)過(guò)濾器。
abstract class AbstractInterceptUrlConfigurer<C extends AbstractInterceptUrlConfigurer<C, H>, H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<C, H> { ... @Override public void configure(H http) throws Exception { FilterInvocationSecurityMetadataSource metadataSource = createMetadataSource(http); if (metadataSource == null) { return; } FilterSecurityInterceptor securityInterceptor = createFilterSecurityInterceptor( http, metadataSource, http.getSharedObject(AuthenticationManager.class)); if (filterSecurityInterceptorOncePerRequest != null) { securityInterceptor .setObserveOncePerRequest(filterSecurityInterceptorOncePerRequest); } securityInterceptor = postProcess(securityInterceptor); http.addFilter(securityInterceptor); http.setSharedObject(FilterSecurityInterceptor.class, securityInterceptor); } ... }
這個(gè)過(guò)濾器的配置器是在 HttpSecurity
的 authorizeRequests()
方法中apply進(jìn)來(lái)的,在我們自己配置的核心配置器中使用的就是該種基于 HttpServletRequest
限制訪(fǎng)問(wèn)的方式。
這個(gè)過(guò)濾器的配置器是 ExceptionHandlingConfigurer
,它自己的 configure()
方法中創(chuàng )建了這個(gè)過(guò)濾器。
public final class ExceptionHandlingConfigurer<H extends HttpSecurityBuilder<H>> extends AbstractHttpConfigurer<ExceptionHandlingConfigurer<H>, H> { ... @Override public void configure(H http) throws Exception { AuthenticationEntryPoint entryPoint = getAuthenticationEntryPoint(http); ExceptionTranslationFilter exceptionTranslationFilter = new ExceptionTranslationFilter( entryPoint, getRequestCache(http)); if (accessDeniedHandler != null) { exceptionTranslationFilter.setAccessDeniedHandler(accessDeniedHandler); } exceptionTranslationFilter = postProcess(exceptionTranslationFilter); http.addFilter(exceptionTranslationFilter); } ... }
這個(gè)過(guò)濾器的配置器是在 HttpSecurity
的 exceptionHandling()
方法中apply進(jìn)來(lái)的,和上面不同的是,這個(gè)過(guò)濾器配置器會(huì )默認被apply進(jìn) HttpSecurity
,在 WebSecurityConfigurerAdapter
中的 init()
方法,里面調用了 getHttp()
方法,這里定義了很多默認的過(guò)濾器配置,其中就包括當前過(guò)濾器配置。
doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
invoke(FilterInvocation fi)
beforeInvocation(Object object)
這個(gè)方法里面有個(gè)
attributes
,里面獲取的就是當前request請求所能匹配中的權限Spel表達式,比如這里是hasRole('ROLE_BUYER')
方法源碼如下,繼續往下走
protected InterceptorStatusToken beforeInvocation(Object object) { ... // 獲取當前request請求所能匹配中的權限Spel表達式 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); ... // Attempt authorization try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } ... }
進(jìn)入:decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
這里有個(gè)投票器,投票結果為1表示可以訪(fǎng)問(wèn)直接返回,投票結果為-1表示拒絕訪(fǎng)問(wèn),向上拋拒絕訪(fǎng)問(wèn)異常,這里使用的投票器是
WebExpressionVoter
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); if (logger.isDebugEnabled()) { logger.debug("Voter: " + voter + ", returned: " + result); } switch (result) { case AccessDecisionVoter.ACCESS_GRANTED: return; case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException(messages.getMessage( "AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
進(jìn)入:vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes)
這里面其實(shí)就是使用Spring的Spel表達式進(jìn)行投票,使用請求中的權限表達式組裝Expression,使用Token令牌中的權限組裝EvaluationContext,然后調用
ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx)
,
public int vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes) { assert authentication != null; assert fi != null; assert attributes != null; WebExpressionConfigAttribute weca = findConfigAttribute(attributes); if (weca == null) { return ACCESS_ABSTAIN; } EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, fi); ctx = weca.postProcess(ctx, fi); return ExpressionUtils.evaluateAsBoolean(weca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED; }
evaluateAsBoolean()
方法里面就是調用Expression的getValue()
方法,獲取實(shí)際的匹配結果,如下圖Spel表達式為hasRole('ROLE_BUYER')
所以它實(shí)際調用的是SecurityExpressionRoot#hasRole
方法(關(guān)于權限表達式對應實(shí)際調用的方法,在《手把手教你如何使用Spring Security(下):訪(fǎng)問(wèn)控制》文章中已貼出,下面文章也補充一份),里面的邏輯其實(shí)就是判斷Token令牌中是否包含有ROLE_BUYER
的角色,有的話(huà)返回true,否則返回false,如下為SecurityExpressionRoot#hasRole
方法源碼:
private boolean hasAnyAuthorityName(String prefix, String... roles) { Set<String> roleSet = getAuthoritySet(); for (String role : roles) { String defaultedRole = getRoleWithDefaultPrefix(prefix, role); if (roleSet.contains(defaultedRole)) { return true; } } return false; }
invoke()
方法,再執行后續過(guò)濾器,未拋異常表示該請求已經(jīng)有訪(fǎng)問(wèn)權限了decide()
方法中會(huì )向上拋拒絕訪(fǎng)問(wèn)異常,一直往上拋直到被處理,往上反向跟蹤發(fā)現這個(gè)過(guò)濾器一直沒(méi)有處理拒絕訪(fǎng)問(wèn)異常,那就繼續往上個(gè)過(guò)濾器拋,就到了我們的異常翻譯過(guò)濾器 ExceptionTranslationFilter
。該過(guò)濾器的 doFilter()
方法很簡(jiǎn)單,沒(méi)有邏輯處理,只對后續過(guò)濾器拋出的異常進(jìn)行處理,源碼如下:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { chain.doFilter(request, response); logger.debug("Chain processed normally"); } catch (IOException ex) { throw ex; } catch (Exception ex) { // Try to extract a SpringSecurityException from the stacktrace Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex); RuntimeException ase = (AuthenticationException) throwableAnalyzer .getFirstThrowableOfType(AuthenticationException.class, causeChain); if (ase == null) { ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType( AccessDeniedException.class, causeChain); } if (ase != null) { handleSpringSecurityException(request, response, chain, ase); } else { // Rethrow ServletExceptions and RuntimeExceptions as-is if (ex instanceof ServletException) { throw (ServletException) ex; } else if (ex instanceof RuntimeException) { throw (RuntimeException) ex; } // Wrap other Exceptions. This shouldn't actually happen // as we've already covered all the possibilities for doFilter throw new RuntimeException(ex); } } }
當拋出拒絕訪(fǎng)問(wèn)異常后,繼續調用 handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception)
方法,方法里面主要將異常信息和錯誤碼設置到響應頭
,然后響應到客戶(hù)端,請求結束。
補充:權限表達式
FilterSecurityInterceptor
,當然這個(gè)是可選的,我們完全也可以自定義一個(gè)過(guò)濾器去處理權限訪(fǎng)問(wèn)。ExceptionTranslationFilter
,里面邏輯很簡(jiǎn)單,給response設置異常信息錯誤碼,再返回給客戶(hù)端。以上就是Spring Security源碼解析之權限訪(fǎng)問(wèn)控制是如何做到的的詳細內容,更多關(guān)于Spring Security權限訪(fǎng)問(wèn)控制的資料請關(guān)注腳本之家其它相關(guān)文章!
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站