• 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登录或者直接返回错误

  • 相关阅读:
    springmvc之异常处理DefaultHandlerExceptionResolver
    springmvc之异常处理ResponseStatusExceptionResolver
    springmvc之异常处理中ExceptionHanderExceptionResolver
    springmvc之多个拦截器方法执行的顺序
    springmvc之配置拦截器拦截相应的请求
    springmvc之自定义拦截器
    springmvc之文件上传
    springmvc之HttpMessageConverter<T>
    构建Java并发模型框架
    Java线程:线程的调度-守护线程——Java线程:线程的调度-合并——Java线程:新特征-障碍器——Java线程:大总结
  • 原文地址:https://www.cnblogs.com/question-sky/p/7065808.html
Copyright © 2020-2023  润新知