欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security源碼解析之權(quán)限訪問控制是如何做到的

 更新時(shí)間:2021年05月17日 10:36:38   作者:木兮同學(xué)  
Spring Security 中對于權(quán)限控制默認(rèn)已經(jīng)提供了很多了,但是,一個(gè)優(yōu)秀的框架必須具備良好的擴(kuò)展性,下面小編給大家介紹Spring Security源碼解析之權(quán)限訪問控制是如何做到的,感興趣的朋友跟隨小編一起看看吧

〇、前文回顧

在實(shí)戰(zhàn)篇《話說Spring Security權(quán)限管理(源碼詳解)》我們學(xué)習(xí)了Spring Security強(qiáng)大的訪問控制能力,只需要進(jìn)行寥寥幾行的配置就能做到權(quán)限的控制,本篇來看看它到底是如何做到的。


一、再聊過濾器鏈

源碼篇中反復(fù)提到,請求進(jìn)來需要經(jīng)過的是一堆過濾器形成的過濾器鏈,走完過濾器鏈未拋出異常則可以繼續(xù)訪問后臺(tái)接口資源,而最后一個(gè)過濾器就是來判斷請求是否有權(quán)限繼續(xù)訪問后臺(tái)資源,如果沒有則會(huì)將拒絕訪問的異常往上向異常過濾器拋,異常過濾器會(huì)對異常進(jìn)行翻譯,然后響應(yīng)給客戶端。

所以,一般情況下最后一個(gè)過濾器是做權(quán)限訪問控制的核心過濾器FilterSecurityInterceptor ,而倒數(shù)第二個(gè)是異常翻譯過濾器ExceptionTranslationFilter ,將異常進(jìn)行翻譯然后響應(yīng)給客戶端。比如我們實(shí)戰(zhàn)項(xiàng)目過濾器鏈圖解

過濾器


二、過濾器的創(chuàng)建

FilterSecurityInterceptor的創(chuàng)建

這個(gè)過濾器的配置器是 ExpressionUrlAuthorizationConfigurer ,它的父類 AbstractInterceptUrlConfigurer 中的 configure() 方法創(chuàng)建了這個(gè)過濾器。

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è)過濾器的配置器是在 HttpSecurityauthorizeRequests() 方法中apply進(jìn)來的,在我們自己配置的核心配置器中使用的就是該種基于 HttpServletRequest 限制訪問的方式。

核心配置器

ExceptionTranslationFilter的創(chuàng)建

這個(gè)過濾器的配置器是 ExceptionHandlingConfigurer ,它自己的 configure() 方法中創(chuàng)建了這個(gè)過濾器。

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è)過濾器的配置器是在 HttpSecurityexceptionHandling() 方法中apply進(jìn)來的,和上面不同的是,這個(gè)過濾器配置器會(huì)默認(rèn)被apply進(jìn) HttpSecurity,在 WebSecurityConfigurerAdapter 中的 init() 方法,里面調(diào)用了 getHttp() 方法,這里定義了很多默認(rèn)的過濾器配置,其中就包括當(dāng)前過濾器配置。

核心配置器


三、源碼流程

FilterSecurityInterceptor

  • 進(jìn)入:doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  • 進(jìn)入:invoke(FilterInvocation fi)
  • 進(jìn)入:beforeInvocation(Object object)

這個(gè)方法里面有個(gè) attributes ,里面獲取的就是當(dāng)前request請求所能匹配中的權(quán)限Spel表達(dá)式,比如這里是 hasRole('ROLE_BUYER')
Spel表達(dá)式 方法源碼如下,繼續(xù)往下走

protected InterceptorStatusToken beforeInvocation(Object object) {
		
		...

		// 獲取當(dāng)前request請求所能匹配中的權(quán)限Spel表達(dá)式
		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è)投票器,投票結(jié)果為1表示可以訪問直接返回,投票結(jié)果為-1表示拒絕訪問,向上拋拒絕訪問異常,這里使用的投票器是 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表達(dá)式進(jìn)行投票,使用請求中的權(quán)限表達(dá)式組裝Expression,使用Token令牌中的權(quán)限組裝EvaluationContext,然后調(diào)用 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() 方法里面就是調(diào)用Expression的 getValue() 方法,獲取實(shí)際的匹配結(jié)果,如下圖Spel表達(dá)式為 hasRole('ROLE_BUYER')
在這里插入圖片描述
所以它實(shí)際調(diào)用的是 SecurityExpressionRoot#hasRole 方法(關(guān)于權(quán)限表達(dá)式對應(yīng)實(shí)際調(diào)用的方法,在《手把手教你如何使用Spring Security(下):訪問控制》文章中已貼出,下面文章也補(bǔ)充一份),里面的邏輯其實(shí)就是判斷Token令牌中是否包含有 ROLE_BUYER 的角色,有的話返回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;
	}
  • 如果投票成功,則會(huì)一直返回到 invoke() 方法,再執(zhí)行后續(xù)過濾器,未拋異常表示該請求已經(jīng)有訪問權(quán)限了
  • 假如投票失敗,在 decide() 方法中會(huì)向上拋拒絕訪問異常,一直往上拋直到被處理,往上反向跟蹤發(fā)現(xiàn)這個(gè)過濾器一直沒有處理拒絕訪問異常,那就繼續(xù)往上個(gè)過濾器拋,就到了我們的異常翻譯過濾器 ExceptionTranslationFilter。

ExceptionTranslationFilter

該過濾器的 doFilter() 方法很簡單,沒有邏輯處理,只對后續(xù)過濾器拋出的異常進(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);
			}
		}
	}

當(dāng)拋出拒絕訪問異常后,繼續(xù)調(diào)用 handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) 方法,方法里面主要將異常信息和錯(cuò)誤碼設(shè)置到響應(yīng)頭,然后響應(yīng)到客戶端,請求結(jié)束。

補(bǔ)充:權(quán)限表達(dá)式

權(quán)限表達(dá)式(ExpressionUrlAuthorizationConfigurer) 說明 Spel表達(dá)式 Spel表達(dá)式實(shí)際執(zhí)行方法(SecurityExpressionOperations)
permitAll() 表示允許所有,永遠(yuǎn)返回true permitAll permitAll()
denyAll() 表示拒絕所有,永遠(yuǎn)返回false denyAll denyAll()
anonymous() 當(dāng)前用戶是anonymous時(shí)返回true anonymous isAnonymous()
rememberMe() 當(dāng)前用戶是rememberMe用戶時(shí)返回true rememberMe isRememberMe()
authenticated() 當(dāng)前用戶不是anonymous時(shí)返回true authenticated isAuthenticated()
fullyAuthenticated() 當(dāng)前用戶既不是anonymous也不是rememberMe用戶時(shí)返回true fullyAuthenticated isFullyAuthenticated()
hasRole(“BUYER”) 用戶擁有指定權(quán)限時(shí)返回true hasRole(‘ROLE_BUYER') hasRole(String role)
hasAnyRole(“BUYER”,“SELLER”) 用于擁有任意一個(gè)角色權(quán)限時(shí)返回true hasAnyRole (‘ROLE_BUYER',‘ROLE_BUYER') hasAnyRole(String… roles)
hasAuthority(“BUYER”) 同hasRole hasAuthority(‘ROLE_BUYER') hasAuthority(String role)
hasAnyAuthority(“BUYER”,“SELLER”) 同hasAnyRole hasAnyAuthority (‘ROLE_BUYER',‘ROLE_BUYER') hasAnyAuthority(String… authorities)
hasIpAddress(‘192.168.1.0/24') 請求發(fā)送的Ip匹配時(shí)返回true hasIpAddress(‘192.168.1.0/24') hasIpAddress(String ipAddress),該方法在WebSecurityExpressionRoot類中
access("@rbacService.hasPermission(request, authentication)") 可以自定義Spel表達(dá)式 @rbacService.hasPermission (request, authentication) hasPermission(request, authentication) ,該方法在自定義的RbacServiceImpl類中

四、總結(jié)

  • 訪問控制的核心過濾器是 FilterSecurityInterceptor ,當(dāng)然這個(gè)是可選的,我們完全也可以自定義一個(gè)過濾器去處理權(quán)限訪問。
  • 處理訪問異常處理的過濾器是 ExceptionTranslationFilter ,里面邏輯很簡單,給response設(shè)置異常信息錯(cuò)誤碼,再返回給客戶端。

以上就是Spring Security源碼解析之權(quán)限訪問控制是如何做到的的詳細(xì)內(nèi)容,更多關(guān)于Spring Security權(quán)限訪問控制的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • idea創(chuàng)建properties文件,解決亂碼問題

    idea創(chuàng)建properties文件,解決亂碼問題

    這篇文章主要介紹了idea創(chuàng)建properties文件,解決亂碼問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Java成員變量與局部變量(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)

    Java成員變量與局部變量(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)

    這篇文章主要介紹了Java成員變量與局部變量的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-04-04
  • java編程SpringSecurity入門原理及應(yīng)用簡介

    java編程SpringSecurity入門原理及應(yīng)用簡介

    Spring 是非常流行和成功的 Java 應(yīng)用開發(fā)框架,Spring Security 正是 Spring 家族中的成員。Spring Security 基于 Spring 框架,提供了一套 Web 應(yīng)用安全性的完整解決方案
    2021-09-09
  • Java利用Strategy模式實(shí)現(xiàn)堆排序

    Java利用Strategy模式實(shí)現(xiàn)堆排序

    策略設(shè)計(jì)模式(Strategy):可以整體的替換一個(gè)算法的實(shí)現(xiàn)部分,能夠整體的替換算法,能讓我們輕松地用不同方法解決同一個(gè)問題。本文將利用Strategy模式實(shí)現(xiàn)堆排序,感興趣的可以學(xué)習(xí)一下
    2022-09-09
  • Spring Boot中Bean定義方調(diào)用方式解析

    Spring Boot中Bean定義方調(diào)用方式解析

    這篇文章主要介紹了Spring Boot中Bean定義方調(diào)用方式解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • 解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問題

    解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問題

    這篇文章主要介紹了解決IDEA集成Docker插件后出現(xiàn)日志亂碼的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-11-11
  • java開發(fā)https請求ssl不受信任問題解決方法

    java開發(fā)https請求ssl不受信任問題解決方法

    這篇文章主要介紹了java開發(fā)https請求ssl不受信任問題解決方法,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • spring security獲取用戶信息的實(shí)現(xiàn)代碼

    spring security獲取用戶信息的實(shí)現(xiàn)代碼

    這篇文章主要介紹了spring security獲取用戶信息的實(shí)現(xiàn)代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • 使用springboot對外部靜態(tài)資源文件的處理操作

    使用springboot對外部靜態(tài)資源文件的處理操作

    這篇文章主要介紹了使用springboot對外部靜態(tài)資源文件的處理操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 淺談SpringMVC國際化支持

    淺談SpringMVC國際化支持

    這篇文章主要介紹了淺談SpringMVC國際化支持,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-03-03

最新評論