SpringSecurity拦截器链的使用详解

 更新时间:2023年11月21日 10:25:21   作者:dalianpai  
这篇文章主要介绍了SpringSecurity拦截器链的使用详解,webSecurity的build方法最终调用的是doBuild方法,doBuild方法调用的是webSecurity的performBuild方法,webSecurity完成所有过滤器的插件,最终返回的是过滤器链代理类filterChainProxy,需要的朋友可以参考下

SpringSecurity拦截器链

Spring版本

<!--Spring Security过滤器链,注意过滤器名称必须叫springSecurityFilterChain-->
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

SpringBoot 版本

拦截器链创建的过程

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.class,
		OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
 
	/**
	 * Controls debugging support for Spring Security. Default is false.
	 * @return if true, enables debug support with Spring Security
	 */
	boolean debug() default false;
}

当我们使用这个注解的时候,等同于把WebSecurityConfiguration类放到了Spring的IOC容器中,在这个时候进行了初始化。

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
        //调用webSecurity的build方法,生成过滤器链。
		return webSecurity.build();
	}

webSecurity的build方法最终调用的是doBuild方法。

public final O build() throws Exception {
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}

doBuild方法调用的市webSecurity的performBuild方法。

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;
 
			beforeInit();
			init();
 
			buildState = BuildState.CONFIGURING;
 
			beforeConfigure();
			configure();
 
			buildState = BuildState.BUILDING;
            //在BUILDING阶段调用webSecurity的performBuild方法
			O result = performBuild();
 
			buildState = BuildState.BUILT;
 
			return result;
		}
	}

webSecurity完成所有过滤器的插件,最终返回的是过滤器链代理类filterChainProxy

@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
						+ "More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
        //会创建要忽略的和要认证的拦截器链
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
        //过滤器链实际是被filterChainProxy代理的
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();
 
		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
		postBuildAction.run();
		return result;
	}

断点图如下:

image-20210508162350146

FilterChainProxy间接继承了Filter,可以作为真正的过滤器使用。它会携带若干条过滤器链,并在承担过滤器职责,将其派发到过滤器链上的每一个过滤器上。

Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		if (clearContext) {
			try {
				request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
                //派发到过滤器链上
				doFilterInternal(request, response, chain);
			}
			finally {
				SecurityContextHolder.clearContext();
				request.removeAttribute(FILTER_APPLIED);
			}
		}
		else {
			doFilterInternal(request, response, chain);
		}
	}
    //是真正执行虚拟过滤器链逻辑的方法。
	private void doFilterInternal(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
 
		FirewalledRequest fwRequest = firewall
				.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse fwResponse = firewall
				.getFirewalledResponse((HttpServletResponse) response);
 
		List<Filter> filters = getFilters(fwRequest);
 
		if (filters == null || filters.size() == 0) {
			if (logger.isDebugEnabled()) {
				logger.debug(UrlUtils.buildRequestUrl(fwRequest)
						+ (filters == null ? " has no matching filters"
								: " has an empty filter list"));
			}
 
			fwRequest.reset();
 
			chain.doFilter(fwRequest, fwResponse);
 
			return;
		}
 
		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}

image-20210508165318181

private VirtualFilterChain(FirewalledRequest firewalledRequest,
				FilterChain chain, List<Filter> additionalFilters) {
			this.originalChain = chain;
			this.additionalFilters = additionalFilters;
			this.size = additionalFilters.size();
			this.firewalledRequest = firewalledRequest;
		}
 
		@Override
		public void doFilter(ServletRequest request, ServletResponse response)
				throws IOException, ServletException {
			if (currentPosition == size) {
				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " reached end of additional filter chain; proceeding with original chain");
				}
 
				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();
 
				originalChain.doFilter(request, response);
			}
			else {
				currentPosition++;
 
				Filter nextFilter = additionalFilters.get(currentPosition - 1);
 
				if (logger.isDebugEnabled()) {
					logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
							+ " at position " + currentPosition + " of " + size
							+ " in additional filter chain; firing Filter: '"
							+ nextFilter.getClass().getSimpleName() + "'");
				}
 
				nextFilter.doFilter(request, response, this);
			}
		}

请求的过程

ApplicationFilterChain是tomcat中的拦截器链,ApplicationFilterChain对象的特点与创建特点:经过一路代码跟踪发现,每一个url匹配模式对应于一个ApplicationFilterChain对象,在应用的整个生命周期中只在第一次被访问时被创建一次,有种单例模式的感觉,但是并没有做线程安全方面的处理,后来发现可能做了池化处理;创建:ApplicationFilterChain对象是在StandardWrapperValve类中的invoke方法中调ApplicationFilterFactory.createFilterChain方法创建的,在实例化完成后紧接着就是为其设置Servlet,添加Filters以及设置其他属性,添加Filters依赖于一个FilterMap[]数组,该数组的赋值与扩容过程在StandardContext类中实现,至于FilterMap的生成,则是在Spring的Bean初始化阶段,通过扫描包下所有的类并结合注解通过反射机制进行实例化的。

@Override
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
 
        if( Globals.IS_SECURITY_ENABLED ) {
            final ServletRequest req = request;
            final ServletResponse res = response;
            try {
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedExceptionAction<Void>() {
                        @Override
                        public Void run()
                            throws ServletException, IOException {
                            internalDoFilter(req,res);
                            return null;
                        }
                    }
                );
            } catch( PrivilegedActionException pe) {
                Exception e = pe.getException();
                if (e instanceof ServletException)
                    throw (ServletException) e;
                else if (e instanceof IOException)
                    throw (IOException) e;
                else if (e instanceof RuntimeException)
                    throw (RuntimeException) e;
                else
                    throw new ServletException(e.getMessage(), e);
            }
        } else {
            internalDoFilter(request,response);
        }
    }
 
private void internalDoFilter(ServletRequest request,
                                  ServletResponse response)
        throws IOException, ServletException {
 
        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            try {
                Filter filter = filterConfig.getFilter();
 
                if (request.isAsyncSupported() && "false".equalsIgnoreCase(
                        filterConfig.getFilterDef().getAsyncSupported())) {
                    request.setAttribute(Globals.ASYNC_SUPPORTED_ATTR, Boolean.FALSE);
                }
                if( Globals.IS_SECURITY_ENABLED ) {
                    final ServletRequest req = request;
                    final ServletResponse res = response;
                    Principal principal =
                        ((HttpServletRequest) req).getUserPrincipal();
 
                    Object[] args = new Object[]{req, res, this};
                    SecurityUtil.doAsPrivilege ("doFilter", filter, classType, args, principal);
                } else {
                    filter.doFilter(request, response, this);
                }
            } catch (IOException | ServletException | RuntimeException e) {
                throw e;
            } catch (Throwable e) {
                e = ExceptionUtils.unwrapInvocationTargetException(e);
                ExceptionUtils.handleThrowable(e);
                sthrow new ServletException(sm.getString("filterChain.filter"), e);
            }
            return;
        }

其中会优先调用springsecurity的拦截器链,它的本质还是有一个fiter,注意,这里第5个过滤器显示在拦截器链后执行的,但是在拦截器链里面执行过了,就不会这执行了。

image-20210508161226433

当请求到拦截链的时候

image-20210508161452455

依次执行过滤器链

image-20210508161551528

到此这篇关于SpringSecurity拦截器链的使用详解的文章就介绍到这了,更多相关SpringSecurity拦截器链内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springboot登陆页面图片验证码简单的web项目实现

    springboot登陆页面图片验证码简单的web项目实现

    这篇文章主要介绍了springboot登陆页面图片验证码简单的web项目实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2019-04-04
  • 解决httpServletRequest.getParameter获取不到参数的问题

    解决httpServletRequest.getParameter获取不到参数的问题

    这篇文章主要介绍了解决httpServletRequest.getParameter获取不到参数的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • Java中Collection与Collections的区别详解

    Java中Collection与Collections的区别详解

    这篇文章主要为大家详细介绍了Java中Collection与Collections的区别,文中有详细的代码示例,具有一定的参考价值,感兴趣的同学可以参考一下
    2023-06-06
  • 详解SpringBoot接收参数的五种形式

    详解SpringBoot接收参数的五种形式

    在Spring Boot中,接收参数可以通过多种方式实现,本文给大家介绍了SpringBoot接收参数的五种形式,并通过代码和图文给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • java中单例模式讲解

    java中单例模式讲解

    这篇文章主要介绍了java中单例模式,本文通过简单的案例,讲解了该模式在java中的使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • Feign如何解决服务之间调用传递token

    Feign如何解决服务之间调用传递token

    这篇文章主要介绍了Feign如何解决服务之间调用传递token,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • 使用Swagger时Controller中api接口显示不全的问题分析及解决

    使用Swagger时Controller中api接口显示不全的问题分析及解决

    swagger是一个十分好用的api接口管理、测试框架,现在越来越多的人使用这个做接口的测试和管理,但经常遇到Controller中的api接口显示不全的问题,所以本文给大家详细分析了问题以及解决方法,需要的朋友可以参考下
    2024-02-02
  • 在Java中读取CSV文件的方式

    在Java中读取CSV文件的方式

    在项目开发中我们经常需要读取csv的内容的操作,读取的逻辑并不复杂,下面这篇文章主要给大家介绍了关于在Java中读取CSV文件的方式,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • Mybatis如何使用@Mapper和@MapperScan注解实现映射关系

    Mybatis如何使用@Mapper和@MapperScan注解实现映射关系

    这篇文章主要介绍了Mybatis使用@Mapper和@MapperScan注解实现映射关系,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java如何向指定文件操作一段内容(增加,删除均可使用本方法)

    Java如何向指定文件操作一段内容(增加,删除均可使用本方法)

    这篇文章主要介绍了Java如何向指定文件操作一段内容(增加,删除均可使用本方法),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12

最新评论