Spring Security源码(一)springSecurityFilterChain的创建与运行

news/2024/7/5 2:20:55

一 前言

整个框架的核心就是构建一个名字为 springSecurityFilterChain 的过滤器Bean,它的类型是FilterChainProxy 。底层通过FilterChainProxy代理去调用各种Filter(Filter链),Filter通过调用AuthenticationManager完成认证 ,通过调用AccessDecisionManager完成授权。
在这里插入图片描述

二 springSecurityFilterChain的加载

springSecurityFilterChain这个Bean是核实加载到spring容器里面?

我们在引入springsecurity的自定义配置是加了@EnableWebSecurity这个注解,这个注解又什么用呢,我们翻开他的源码看看

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
		HttpSecurityConfiguration.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;

}

这里它通过@import引入springsecurity的框架的响应的配置,并加载到spring容器中,完成了响应的配置,核心是WebSecurityConfiguration这个类

2.1 springSecurityFilterChain的创建

在WebSecurityConfiguration我们发现会创建一个springSecurityFilterChain的bean

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !this.securityFilterChains.isEmpty();
		Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			this.webSecurity.apply(adapter);
		}
		for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
			this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
			customizer.customize(this.webSecurity);
		}
		return this.webSecurity.build();
	}

建造者 webSecurity 调用 build() 方法,开始构建 springSecurityFilterChain
那么webSecurity在理创建的呢?
在这个类中有一个setFilterChainProxySecurityConfigurer()通过他完成webSecurity的创建

@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
		if (this.debugEnabled != null) {
			this.webSecurity.debug(this.debugEnabled);
		}
		webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
		Integer previousOrder = null;
		Object previousConfig = null;
		for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
						+ " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			this.webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

在build方法里面干了啥呢
AbstractSecurityBuilder调用doBuild
在这里插入图片描述
调用org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder中的doBuild方法,通过performBuild()完成springSecurityFilterChain的创建
在这里插入图片描述
在org.springframework.security.config.annotation.web.builders.WebSecurity完成核心创建

public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
		SecurityBuilder<Filter>, ApplicationContextAware {
		
	private final List<RequestMatcher> ignoredRequests = new ArrayList<RequestMatcher>();
	private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();

	...
	@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<SecurityFilterChain>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
		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;
	}
	...
}
  • 主要关注由 AbstractConfiguredSecurityBuilder 继承下来的方法实现 performBuild()
  • 方法中首先创建了一个 securityFilterChains
  • 然后第一个for循环将那些忽略拦截的URL封装成一堆 DefaultSecurityFilterChain 添加进 securityFilterChains
  • 第二个for循环调用的是 build() 方法,其实它最终调用的是 HttpSecurity 实现的 performBuild() 方法,返回值也是 DefaultSecurityFilterChain,随后添加进 securityFilterChains
  • 最后根据 securityFilterChains 创建出 FilterChainProxy 对象

注意创建过程可不是这么简单的由 webSecurity 一次性调用下来,就是说 doBuild() 可不只是被 webSecurity 对象调用,会被很多创建者重复调用,重复的去走创建流程,比如被一堆的 HttpSecurity 调用目的是创建单条过滤器链,可以自己Debug去走一走。

由此securityFilterChains就创建完成

2.2springSecurityFilterChain加载到Servlet 容器

通过上面我们知道springSecurityFilterChain创建完成了,那么2.2springSecurityFilterChain是如何加载到Servlet 容器中?

这里我们先了接下SpringBoot 中 Filter 加入 Servlet 容器的方式的几种方式

方式一 使用 @WebFilter 注解,@ServletComponentScan 所扫描的包路径必须包含该 Filter

@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
    }

    @Override
    public void destroy() {
    }
}

@SpringBootApplication
@EnableAutoConfiguration
@EnableWebMvc
@ServletComponentScan(basePackages = "com.lktest.filter")
public class EghmApplication {

    public static void main(String[] args) {
        SpringApplication.run(EghmApplication.class, args);
    }
}

方式二 通过FilterRegistrationBean 注入

@Configuration
public class FilterConfig {
    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean bean = new FilterRegistrationBean();
        bean.setFilter(new MyFilter2());
        bean.addUrlPatterns("/*");
        return bean;
    }
}

方式三 通过DelegatingFilterProxyRegistrationBean 注入

	@Bean("proxyFilter")
    public Filter filter (){
        return new Filter() {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
            }

            @Override
            public void destroy() {
            }
        };
    }

    @Bean
    public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
        DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("proxyFilter");
        bean.addUrlPatterns("/*");
        return bean;
    }

第二种和第三种类似,均实现了 AbstractFilterRegistrationBean 接口,而该接口间接实现了 ServletContextInitializer ,SpringBoot 在启动容器后会查找实现该接口的 Bean,并调用 onStartup() 方法添加自定义的 Filter,两者的区别:

  • DelegatingFilterProxyRegistrationBean 通过传入的 proxyFilter 名字,在 WebApplicationContext 查找该 Fillter Bean,并通过DelegatingFilterProxy 生成基于该 Bean 的代理 Filter 对象。
  • FilterRegistrationBean 则是直接设置一个Filter,因此该 Filter 可以有 Spring 容器管理,也可不用 Spring 管理

方式四 如果 Filter 声明为一个 Bean,则不需要定义为 FilterRegistrationBean,也会被 Spring 发现并添加,就是方法四,该方式无法定义拦截规则等,默认全局,慎用。

	@Bean("myFilter")
	public Filter filter() {
		return new Filter() {
			@Override
			public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
			}

			@Override
			public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
			}

			@Override
			public void destroy() {
			}
		};
	}

2.2.1 核心过滤器是如何加入 Sevlet 容器

其实核心过滤器的加入方式使用的是上面提到的方式三,SpringBoot 提供了这么一个类:SecurityFilterAutoConfiguration

@Configuration(proxyBeanMethods = false)
// 仅在 Servlet 环境下生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 确保安全属性配置信息被加载并以 bean 形式被注册到容器
@EnableConfigurationProperties(SecurityProperties.class)
// 仅在特定类存在于 classpath 上时才生效
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
// 指定该配置类在 SecurityAutoConfiguration 配置类应用之后应用
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

	// 要注册到 Servlet 容器的 DelegatingFilterProxy Filter的目标代理 Filter Bean 的名称:springSecurityFilterChain,
	// 会发现实际上就是我们的核心过滤器的 Bean。
	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	// 定义一个 securityFilterChainRegistration 的 Bean,其目的是注册另外一个 Bean 到 Servlet 容器
	// 实现类为 DelegatingFilterProxy 的一个 Servlet Filter,该 DelegatingFilterProxy Filter 其实是一个代理过滤器
	// 它被 Servlet 容器用于匹配特定URL模式的请求,而它会将任务委托给名字为 springSecurityFilterChain 的 Filter
	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}

	private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
		if (securityProperties.getFilter().getDispatcherTypes() == null) {
			return null;
		}
		return securityProperties.getFilter().getDispatcherTypes().stream()
				.map((type) -> DispatcherType.valueOf(type.name()))
				.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
	}

}
  • 该类主要定义了一个 securityFilterChainRegistration 的 Bean,其目的是注册另外一个 Bean 到 Servlet 容器,实现类为 DelegatingFilterProxy 的一个 Servlet Filter
  • 这个 DelegatingFilterProxy Filter 其实是一个代理过滤器,它被 Servlet 容器用于匹配特定 URL 模式的请求,而它会将任务委托给名字为 springSecurityFilterChain 的 Filter
  • 正好,我们 Spring Security 的核心过滤器名字就是 springSecurityFilterChain,其实现类是 FilterChainProxy

三 运行过程

public class FilterChainProxy extends GenericFilterBean {
//省略....

   // Servlet 容器调用FilterChainProxy 的该方法
	@Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
		if (!clearContext) {
			// 真正执行的过程
			doFilterInternal(request, response, chain);
			return;
		}
		try {
			request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
			doFilterInternal(request, response, chain);
		}
		catch (RequestRejectedException ex) {
			this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
		}
		finally {
			SecurityContextHolder.clearContext();
			request.removeAttribute(FILTER_APPLIED);
		}
	}

	private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
		HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
		// 获得当前请求所有的过滤器链
		List<Filter> filters = getFilters(firewallRequest);
		if (filters == null || filters.size() == 0) {
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
			}
			firewallRequest.reset();
			chain.doFilter(firewallRequest, firewallResponse);
			return;
		}
		if (logger.isDebugEnabled()) {
			logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
		}
		// 将过滤器链加载到VirtualFilterChain
		VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
		// 调用VirtualFilterChain doFilter()执行过滤器中方法
		virtualFilterChain.doFilter(firewallRequest, firewallResponse);
	}
	//VirtualFilterChain 实现FilterChain 
	private static final class VirtualFilterChain implements FilterChain {

		private final FilterChain originalChain;

		private final List<Filter> additionalFilters;

		private final FirewalledRequest firewalledRequest;

		private final int size;

		private int currentPosition = 0;

		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 (this.currentPosition == this.size) {
				if (logger.isDebugEnabled()) {
					logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
				}
				// Deactivate path stripping as we exit the security filter chain
				this.firewalledRequest.reset();
				this.originalChain.doFilter(request, response);
				return;
			}
			// 通过责任链模式完成过滤器链的调用
			this.currentPosition++;
			Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
						this.currentPosition, this.size));
			}
			nextFilter.doFilter(request, response, this);
		}

	}

}
  • 通过以上的分析我们知道了FilterChainProxy过滤器已经加入了 ServletContext,请求到达的时候会调用 doFilter()
  • doFilter() 里面进入方法 doFilterInternal(),里面 getFilters() 方法返回第一条匹配中请求的过滤器链,然后使用过滤器链(filters)和请求(fwRequest)以及原始过滤器链(chain)共同创建出类型为 VirtualFilterChain 虚拟过滤器链对象 vfc,目的是为了在过滤器链上传递请求,这里便是典型的责任链模式
  • 之后请求开始被所有 Filter 处理,所有 Filter 都处理过之后会使用原始过滤器调用 doFilter(request, response) 继续被其他的过滤器处理

http://lihuaxi.xjx100.cn/news/240408.html

相关文章

不断的困惑:为什么我仍然使用JavaScript函数语句

Back in the late 90’s — when I learned JavaScript — we were taught to write the “Hello World” function using a function statement. Like this…上世纪90年代后期(当我学习JavaScript时)&#xff0c;我们被教导使用函数语句编写“ Hello World”函数。 像这样… …

css样式分类

1.内联式&#xff08;直接在标签里写属性&#xff0c;属性名为style&#xff09; 2.内嵌式 (直接嵌入到head标签里面&#xff0c;以标签形式出现&#xff0c;标签名为style&#xff09; 选择器&#xff1a;用来选择标签 1&#xff1a;用标签选择 2&#xff1a;用ID选择 关键符…

npm should be run outside of the Node.js REPL, in your normal shell

错误&#xff1a; npm should be run outside of the Node.js REPL, in your normal shell 在搭建vue环境时报错&#xff0c; 设置缓存文件夹 npm config set cache "D:\vueProject\nodejs\node_cache"和 设置全局模块存放路径 npm config set prefix “D:\vueProjec…

《数据科学R语言实践:面向计算推理与问题求解的案例研究法》一一2.1 引言...

本节书摘来自华章计算机《数据科学R语言实践&#xff1a;面向计算推理与问题求解的案例研究法》一书中的第2章&#xff0c;第2.1节,作者&#xff1a;[美] 德博拉诺兰&#xff08;Deborah Nolan&#xff09;  邓肯坦普朗&#xff08;Duncan Temple Lang&#xff09;  更多章…

vs2017 open从v_宣布#Open2017,这是面向开发人员的除夕直播流

vs2017 open从vHere are a few reasons to stay home this New Year’s Eve:这是除夕之夜留在家里的一些理由&#xff1a; It’s the worst day of the year for fatal drunk driving deaths 这是致命的酒后驾车致死的一年中最糟糕的一天 It’s crowded 拥挤 It’s freaking c…

向下滚动页面导航悬浮

为什么80%的码农都做不了架构师&#xff1f;>>> 做两个导航&#xff0c;第二个隐藏 下拉到一定位置&#xff0c;显示第二个&#xff0c;position:fixed $(function(){$(window).scroll(function () {var top $(document).scrollTop();var m$(".nav")…

我国网络安全人才培养缺口巨大

近日在武汉举行的国家网络安全宣传周的相关论坛上&#xff0c;我国网络安全人才培养缺口巨大成为与会专家热议的话题。来自中央和地方相关部门、高校研究者、互联网企业代表均认为&#xff0c;我国网络安全人才输出仍距国家、企业需求有较大差距。 去年&#xff16;月&#xff…

输入vue ui没反应

在cmd中输入 vue ui没有反应 输入 vue -h查看&#xff0c;发现是版本太低&#xff0c;根本没有ui 这是因为vue的版本太低导致的&#xff0c; 输入cnpm i -g vue/cli 升级脚手架即可 升级完成后&#xff0c;输入vue -h 最后输入vue ui即可