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

Spring Security源碼解析之權限訪問控制是如何做到的

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

〇、前文回顧

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


一、再聊過濾器鏈

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

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

過濾器


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

FilterSecurityInterceptor的創(chuàng)建

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

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);
	}
	...
}

這個過濾器的配置器是在 HttpSecurityauthorizeRequests() 方法中apply進來的,在我們自己配置的核心配置器中使用的就是該種基于 HttpServletRequest 限制訪問的方式。

核心配置器

ExceptionTranslationFilter的創(chuàng)建

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

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);
	}
	...
}

這個過濾器的配置器是在 HttpSecurityexceptionHandling() 方法中apply進來的,和上面不同的是,這個過濾器配置器會默認被apply進 HttpSecurity,在 WebSecurityConfigurerAdapter 中的 init() 方法,里面調用了 getHttp() 方法,這里定義了很多默認的過濾器配置,其中就包括當前過濾器配置。

核心配置器


三、源碼流程

FilterSecurityInterceptor

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

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

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;
		}
		
		...
		
	}

進入:decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)

這里有個投票器,投票結果為1表示可以訪問直接返回,投票結果為-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();
	}

進入:vote(Authentication authentication, FilterInvocation fi, Collection<ConfigAttribute> attributes)

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

ExceptionTranslationFilter

該過濾器的 doFilter() 方法很簡單,沒有邏輯處理,只對后續(xù)過濾器拋出的異常進行處理,源碼如下:

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);
			}
		}
	}

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

補充:權限表達式

權限表達式(ExpressionUrlAuthorizationConfigurer) 說明 Spel表達式 Spel表達式實際執(zhí)行方法(SecurityExpressionOperations)
permitAll() 表示允許所有,永遠返回true permitAll permitAll()
denyAll() 表示拒絕所有,永遠返回false denyAll denyAll()
anonymous() 當前用戶是anonymous時返回true anonymous isAnonymous()
rememberMe() 當前用戶是rememberMe用戶時返回true rememberMe isRememberMe()
authenticated() 當前用戶不是anonymous時返回true authenticated isAuthenticated()
fullyAuthenticated() 當前用戶既不是anonymous也不是rememberMe用戶時返回true fullyAuthenticated isFullyAuthenticated()
hasRole(“BUYER”) 用戶擁有指定權限時返回true hasRole(‘ROLE_BUYER') hasRole(String role)
hasAnyRole(“BUYER”,“SELLER”) 用于擁有任意一個角色權限時返回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匹配時返回true hasIpAddress(‘192.168.1.0/24') hasIpAddress(String ipAddress),該方法在WebSecurityExpressionRoot類中
access("@rbacService.hasPermission(request, authentication)") 可以自定義Spel表達式 @rbacService.hasPermission (request, authentication) hasPermission(request, authentication) ,該方法在自定義的RbacServiceImpl類中

四、總結

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

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

相關文章

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

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

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

    Java成員變量與局部變量(動力節(jié)點Java學院整理)

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

    java編程SpringSecurity入門原理及應用簡介

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

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

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

    Spring Boot中Bean定義方調用方式解析

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

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

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

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

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

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

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

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

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

    淺談SpringMVC國際化支持

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

最新評論