Springboot security cas源碼陶冶-FilterSecurityInterceptor


前言:用戶登錄信息校驗成功后,都會獲得當前用戶所擁有的全部權限,所以對訪問的路徑當前用戶有無權限則需要攔截驗證一發

Spring security過濾器的執行順序

首先我們需要驗證為啥FilterSecurityInterceptor會在UsernamePassowrdAuthenticationFilter/CasAuthenticationFilter之后,這里則可以去看下spring security包下的FilterComparator的構造函數便可以得知

FilterComparator() {
		int order = 100;
		****
		****
		order += STEP;
		put(CorsFilter.class, order);
		order += STEP;
		put(CsrfFilter.class, order);
		order += STEP;
		put(LogoutFilter.class, order);
		order += STEP;
		put(X509AuthenticationFilter.class, order);
		order += STEP;
		put(AbstractPreAuthenticatedProcessingFilter.class, order);
		order += STEP;
				filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
				order);
		order += STEP;
		put(UsernamePasswordAuthenticationFilter.class, order);
		order += STEP;
		put(ConcurrentSessionFilter.class, order);
		order += STEP;
		filterToOrder.put(
				"org.springframework.security.openid.OpenIDAuthenticationFilter", order);
		order += STEP;
		****
		****
		order += STEP;
		put(AnonymousAuthenticationFilter.class, order);
		order += STEP;
		put(SessionManagementFilter.class, order);
		order += STEP;
		put(ExceptionTranslationFilter.class, order);
		order += STEP;
		put(FilterSecurityInterceptor.class, order);
		order += STEP;
		put(SwitchUserFilter.class, order);
	}

另外FilterComparator#compare()方法表明是按照order的從小到大排序,所以Filter的執行順序便一目了然,重要的Filter執行順序如下

LogoutFilter-->CasAuthenticationFilter-->
UsernamePasswordAuthenticationFilter-->
AnonymousAuthenticationFilter-->ExceptionTranslationFilter-->
FilterSecurityInterceptor

FilterSecurityInterceptor#doFilter()-執行邏輯

代碼如下

	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		FilterInvocation fi = new FilterInvocation(request, response, chain);
		invoke(fi);
	}

進而查看下FilterSecurityInterceptor#invoke()方法

	//主要展示了具體的邏輯,涉及到父類方法的調用
	public void invoke(FilterInvocation fi) throws IOException, ServletException {
		//對同一個請求的多次訪問則放行
		if ((fi.getRequest() != null)
				&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
				&& observeOncePerRequest) {
			fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
		}
		else {
			//第一次訪問則需要攔截驗證
			if (fi.getRequest() != null) {
				fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
			}
			/**
			**調用父類AbstractSecurityInterceptor方法進行校驗
			**
			*/
			InterceptorStatusToken token = super.beforeInvocation(fi);

			try {
				fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
			}
			finally {
				//是否需要重新設置spring的安全上下文SecurityContext
				super.finallyInvocation(token);
			}
			//處理@PostAuthorize and @PostFilter注解
			super.afterInvocation(token, null);
		}
	}

下面針對父類的方法進行分析

AbstractSecurityInterceptor#beforeInvocation-執行主要校驗工作

由於代碼偏長,截取重要代碼片段分析

	protected InterceptorStatusToken beforeInvocation(Object object) {
		Assert.notNull(object, "Object was null");
		***
		***
		//一般通過SecurityMetadataSource對象獲取當前用戶訪問路徑對應的角色
		Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);
		//為空則拋異常或者返回null
		if (attributes == null || attributes.isEmpty()) {
			if (rejectPublicInvocations) {
				throw new IllegalArgumentException(
						"Secure object invocation "
								+ object
								+ " was denied as public invocations are not allowed via this interceptor. "
								+ "This indicates a configuration error because the "
								+ "rejectPublicInvocations property is set to 'true'");
			}

			if (debug) {
				logger.debug("Public object - authentication not attempted");
			}

			publishEvent(new PublicInvocationEvent(object));

			return null; // no further work post-invocation
		}

		if (debug) {
			logger.debug("Secure object: " + object + "; Attributes: " + attributes);
		}
		//如果沒有驗證過則拋出AuthenticationException異常
		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			credentialsNotFound(messages.getMessage(
					"AbstractSecurityInterceptor.authenticationNotFound",
					"An Authentication object was not found in the SecurityContext"),
					object, attributes);
		}
		//對於非token和非login請求
		//一般都會有默認的AnonymousAuthenticationFilter使其不再校驗
		//所以此處一般不需要再次校驗
		Authentication authenticated = authenticateIfRequired();

		// Attempt authorization 嘗試授權
		try {
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));
			//對於授權失敗則會拋出異常,這個異常會由ExceptionTranslationFilter獲取
			throw accessDeniedException;
		}
		****
		****

		// 默認不處理,runAs返回null
		Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
				attributes);

		if (runAs == null) {
			//直接返回
			return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
					attributes, object);
		}
		else {
			if (debug) {
				logger.debug("Switching to RunAs Authentication: " + runAs);
			}

			SecurityContext origCtx = SecurityContextHolder.getContext();
			SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
			SecurityContextHolder.getContext().setAuthentication(runAs);

			// need to revert to token.Authenticated post-invocation
			return new InterceptorStatusToken(origCtx, true, attributes, object);
		}
	}

小結

FilterSecurityInterceptor實現的作用有

  1. SecurityMetadataSource對象來獲取當前訪問路徑對應的角色集合Collection<ConfigAttribute> attributes

  2. AccessDecisionManager對象來對獲取到的角色集合進行校驗,與Authentication.getAuthorities()集合進行對照

  3. 驗證與授權過程中產生的異常AuthenticationExceptionAccessDeniedException會被ExceptionTranslationFilter攔截處理,從而請求casServer登錄或者直接返回錯誤


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM