首页 > 编程语言 >spring security自动配置的源码简单分析

spring security自动配置的源码简单分析

时间:2023-04-09 17:00:14浏览次数:40  
标签:spring public 源码 filters 过滤器 new security 方法 class

本文基于的springboot版本是 2.1.3.RELEASE,用springboot来自动配置spring security,

一、综述

在springboot中使用spring security只需要引入如下依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

spring security 是基于过滤器和过滤器链实现对应用的认证和权限控制的,所以研究springboot如何自动配置security也需要从它如何配置过滤器来入手。

二、SecurityFilterAutoConfiguration

和springboot的其他starter类似,spring security也有许多自动配置类,这个就是用来自动配置拦截入口过滤器的。

看一下其中的部分代码

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
		SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {

	private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

	@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
						.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
	}

}

首先看到这个类中有一个常量 DEFAULT_FILTER_NAME,值=springSecurityFilterChain ,

这个类中还创建了一个DelegatingFilterProxyRegistrationBean类型的bean,这个bean的作用就是用来给我们的web应用中注册一个类型是DelegatingFilterProxy的过滤器,(这和在springboot应用中注册普通过滤器的方式是一样的,注册普通过滤器时给容器中注册的是FilterRegistrationBean )

这个DelegatingFilterProxy 从名字看就知道是一个代理过滤器,它会把请求代理给内部的目标过滤器,这个目标过滤器的名称是通过DelegatingFilterProxyRegistrationBean的构造方法传递过去的,也就是上边提到的

springSecurityFilterChain

总结一下就是 SecurityFilterAutoConfiguration这个配置类会给应用中注册一个代理过滤器 DelegatingFilterProxy ,代理的目标是名称为springSecurityFilterChain 的过滤器。

接着来看下DelegatingFilterProxy 的部分源码,来研究下它是怎么根据一个过滤器名称来做代理的。

public class DelegatingFilterProxy extends GenericFilterBean {
    
    @Nullable
	private String targetBeanName;//被代理的过滤器名称
    
    @Nullable
	private volatile Filter delegate;//被代理的过滤器
    //接收被代理过滤器名称的构造方法
    public DelegatingFilterProxy(String targetBeanName) {
		this(targetBeanName, null);
	}
    
    //doFilter方法,真正的过滤逻辑
    @Override
	public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {

		// Lazily initialize the delegate if necessary.
		Filter delegateToUse = this.delegate;
		if (delegateToUse == null) {
			synchronized (this.delegateMonitor) {
				delegateToUse = this.delegate;
                 /*如果被代理对象还没有被赋值,就从spring容器中根据名称也就是构造方法传进来的名称
                 取目标过滤器
                 */
				if (delegateToUse == null) {
                    // 得到spring容器
					WebApplicationContext wac = findWebApplicationContext();
					if (wac == null) {
						throw new IllegalStateException("No WebApplicationContext found: " +
								"no ContextLoaderListener or DispatcherServlet registered?");
					}
                      //从容器中取代理目标
					delegateToUse = initDelegate(wac);
				}
				this.delegate = delegateToUse;
			}
		}

		// Let the delegate perform the actual doFilter operation.
         //执行代理目标的具体逻辑
		invokeDelegate(delegateToUse, request, response, filterChain);
	}
    
    ...省略其他
}

上边的源码说明了DelegatingFilterProxy在它的doFilter方法中会根据targetBeanName从spring容器中取出一个过滤器代理它的doFilter逻辑,在springsecurity中就是取出名为springSecurityFilterChain的过滤器。

所以说springsecurity自动配置时肯定还有别的配置给容器中添加了这么一个名为springSecurityFilterChain的过滤器,接下来我来研究这部分。

三、SecurityAutoConfiguration

看下这个自动配置类

@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(
			ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

重点关注这个类import了一个类 WebSecurityEnablerConfiguration.class

而这个类上加了 @EnableWebSecurity注解,重点就在这个注解上

@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {

}

这个注解的源码导入了spring security的另一个配置类 WebSecurityConfiguration.class,而在这个配置类中做了许多事情。

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

3.1 WebSecurityConfiguration

所以我接着来翻下WebSecurityConfiguration.class这个类的部分源码。

@Configuration
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
    //这个属性就是用来创建springSecurityFilterChain这个过滤器的
    private WebSecurity webSecurity;
	
    //这个方法就是在给容器中添加上边提到的springSecurityFilterChain过滤器
    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
            //这个if的意思是如果用户没有添加自己的WebSecurityConfigurer,就在这里创建一个
            //添加到webSecurity对象中
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
        //重点关注这一句,执行webSecurity.build()方法来构建出一个Filter对象。
        // 其中webSecurity就是一个用来创建springSecurityFilterChain的构建器,具体的后边第5节细说
		return webSecurity.build();
	}
    
    //在关注下一个set方法,这个方法是用来创建webSecurity这个构建器对象的
    //第二个方法参数webSecurityConfigurers是利用spring内部的注入方式从容器中收集到所有的针对
    //webSecurity的配置信息
	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
        //这个方法的作用就是创建webSecurity,所以在这里new了一个,先不需要关注objectPostProcessor
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}
		//下边这段代码先不关注,看主要的逻辑
		Collections.sort(webSecurityConfigurers, 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;
		}
        //这部门内容第9节时还会讲,可以先不看
        //把从容器中获取到的配置对象webSecurityConfigurers循环添加到构建器webSecurity中
        //然后上边那个创建过滤器的方法执行webSecurity.build方法时就会应用这里添加的配置对象
        //去对Filter做一些配置然后得到想要的Filter
        // springsecurity是一个高度封装的框架,通过指定配置对象可以针对过虑器做调整。
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
            // 把从容器中获取到的webSecurityConfigurer添加到新创建出来的webSecurity对象中
			webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}    
}

上边提到了构建器webSecurity和构建器的配置对象(SecurityConfigurer)接下来继续对这两个做一些分析。

四、SecurityBuilder和SecurityConfigurer

SecurityBuilder是一个接口,是springsecurity提供的一个安全对象构建器,向上边用来创建springSecurityFilterChain的webSecurity就实现了它

public interface SecurityBuilder<O> {

	/**
	 * Builds the object and returns it or null.
	 *
	 * @return the Object to be built or null if the implementation allows it.
	 * @throws Exception if an error occurred when building the Object
	 */
	O build() throws Exception;
}

接着来看下SecurityConfigurer接口,它是用来对构建器对象进行配置的,其中提供了两个方法在不同阶段对构建器对象做配置,方法参数就是构建器对象SecurityBuilder


public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {


	void init(B builder) throws Exception;


	void configure(B builder) throws Exception;
}

SecurityBuilder的继承体系是这样的,spring security提供了两个抽象类实现了它,它是构建器的顶层接口

SecurityBuilder <--- AbstractSecurityBuilder <--- AbstractConfiguredSecurityBuilder,

在AbstractSecurityBuilder中实现了build方法并封装了一个doBuild方法

public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
	private AtomicBoolean building = new AtomicBoolean();

	private O object;
    //build方法的实现
	public final O build() throws Exception {
        //这个方法中用cas操作保证了多线程环境下一个构建器的build方法只会被执行一次
		if (this.building.compareAndSet(false, true)) {
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}
    //提供给子类的具体的创建逻辑
    protected abstract O doBuild() throws Exception;
}

接下来看AbstractConfiguredSecurityBuilder,这个类中对doBuild方法做了实现,对构建器对象的扩展配置就是通过这个方法完成的

public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {

//这个成员变量configurers中保存了给这个构建器添加的所有配置对象SecurityConfigurer
//看这个时先不要去深究泛型,关注它主要的作用就行
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>>();
	

	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			//BuildState这个枚举标记了当前构建器创建对象过程中的不同状态
			buildState = BuildState.INITIALIZING;
			//一个空方法,子类可以自主选择是否实现
			beforeInit();
			//取出当前构建器中添加的所有配置对象SecurityConfigurer然后执行其init方法
			init();
			//改变状态
			buildState = BuildState.CONFIGURING;
			//一个空方法,子类可以自主选择是否实现
			beforeConfigure();
			//取出当前构建器中添加的所有配置对象SecurityConfigurer然后执行其congfig方法
			configure();

			buildState = BuildState.BUILDING;
			/*上边的方法完成对当前securityBuilder对象的配置,这里是真正创建出需要的对象,
			是个抽象方法,子类必须实现
			*/
			O result = performBuild();
			//改变状态
			buildState = BuildState.BUILT;

			return result;
		}
	}
	//这个add方法用来给当前构建器对象添加配置对象SecurityConfigurer
	private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
		Assert.notNull(configurer, "configurer cannot be null");

		Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
				.getClass();
		//先关注主要逻辑,理清整体的脉络
		synchronized (configurers) {
			if (buildState.isConfigured()) {
				throw new IllegalStateException("Cannot apply " + configurer
						+ " to already built object");
			}
			List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
					.get(clazz) : null;
			if (configs == null) {
				configs = new ArrayList<SecurityConfigurer<O, B>>(1);
			}
			configs.add(configurer);
			//添加configurer
			this.configurers.put(clazz, configs);
			if (buildState.isInitializing()) {
				//这个的意思是当构建器已经开始构建到这个阶段时再添加进来的配置类会放到这里,它的
				// init和config方法还是可以被执行的。
				this.configurersAddedInInitializing.add(configurer);
			}
		}
	}
    
    //init,config,和其他方法的源码就不贴了,先知道他们是干什么的就可以理解我们的后续逻辑
}

五、WebSecurity

上边第三节的时候提到了webSecurity这个构建器用来创建Filter,现在我来分析下这个的源码,

这个类继承了AbstractConfiguredSecurityBuilder,第一个泛型表示要创建出的对象,第二个泛型表示用来创建此对象的构建器,又实现了SecurityBuilder个人理解是为了方便突出这个构建器是为了创建什么对象。

我重点看下这个类的performBuild方法,这个方法真正的创建出对象

public final class WebSecurity extends
		AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements
    SecurityBuilder<Filter>, ApplicationContextAware {
    
    	@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循环是遍历构建器集合securityFilterChainBuilders,执行build方法创建过滤器链
        SecurityFilterChain的对象,这个对象和它的构建器我们下文再研究。第7节有详细介绍
        */
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
            //循环执行构建器的build方法构造出SecurityFilterChain对象添加到securityFilterChains中
            // 第七节有详细介绍
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
        /*这块创建了一个过滤器 FilterChainProxy,最后会把这个返回,
        所以在WebSecurityConfiguration.springSecurityFilterChain()方法中最终会得到这个类型的
        过滤器然后放入spring容器中。
        注意这个入参 securityFilterChains,这是一个过滤器链条的集合,所以说明spring security
        是支持配置多个过滤器链条的,每一个过滤器链条中会有多个过滤器来处理我们的请求,至于
        FilterChainProxy是如何执行过滤器链中的过滤器的我们下文再研究。
        */
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
        //这一段是debug相关的可以不关
		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;
	}
}

继续写SecurityFilterChain和它的构建器

六、请求到达spring security的处理过程

这一节来记录下一个请求到达spring security后的处理流程。

上边第二节的时候提到了DelegatingFilterProxy 这个代理过滤器,一个请求到达spring security后首先会被这个过滤器拦截到,这是一个代理过滤器,它会使用内部的代理目标delegate 也就是从容器中获取名称是springSecurityFilterChain的过滤器,也就是第五节提到的由WebSecurity 构建的FilterChainProxy这个类型的过滤器来处理请求,总结下请求的处理路径就是

1.DelegatingFilterProxy ----->2.FilterChainProxy

6.1 FilterChainProxy

接下来详细看下FilterChainProxy这个过滤器

分析下期中的部分源码

public class FilterChainProxy extends GenericFilterBean {
    /*
    这个属性很重要,过滤器链的集合,从这个属性的类型可以知道spring security支持多个过滤器链
    一个链条对象SecurityFilterChain中会包含多个过滤器,
    当然一般情况我们的应用中只需要配置一个链条对象
    */
    private List<SecurityFilterChain> filterChains;
    
    @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);

        /* 首先通过这个getFilters方法来从过滤器链的集合filterChains中匹配到一个过滤器链
           对象SecurityFilterChain,返回这个链条里边的所有过滤器
        */
		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;
		}
		 // 根据filters构造虚拟过滤器链对象VirtualFilterChain来遍历执行filters中的每个过滤器
         // VirtualFilterChain是当前类中的内部类,遍历的原理就是这个类
		VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
		vfc.doFilter(fwRequest, fwResponse);
	}
    
    // 这个方法用来根据request对象从filterChains中匹配出一个SecurityFilterChain对象并返回
    // 此链条中的所有过滤器
    private List<Filter> getFilters(HttpServletRequest request) {
		//从这个方法可以看出SecurityFilterChain中一定存在两个方法 matches,getFilters
        for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}
		return null;
	}
    
    //内部类用来依次执行上边getFilters方法获取到的filters中的所有过滤器
    private static class VirtualFilterChain implements FilterChain {
		/* 这个变量记录了原始的FilterChain对象,也就是FilterChainProxy这个过滤器的doFilter方法中的参            数FilterChain,通过这个FilterChain可以把请求从FilterChainProxy转给其他的过滤器(请求已经被         spring security处理完了接着去执行其他的过滤器)
		
		这里需要普及一下Filter的相关知识,当我们的应用中配置多个过滤器(不仅是spirng security的过滤器)         时,是通过FilterChain实现从filter1转给filter2的
		下面是Filter接口的源码
        public interface Filter {
        
           //这个方法参数chain就是用来实现把请求从filter1转给filter2的
            public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException;
        }
        
        一般我们实现Filter接口时都要实现此方法,并通过chain.doFilter方法实现把请求交给下一个过滤器。
        public  MyFilter implements Filter{
        	@Override
        	public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException{
            
              ...一些过滤的逻辑
              //最后把请求交给下一个顾虑器时都会执行
              chain.doFilter(request, response);
            }
        }
        
		*/
        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 {
            //这个方法通过currentPosition来控制递归调用additionalFilters中的所有过滤器
			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();
				// spring security已经处理完了,把请求转给其他的过滤器
				originalChain.doFilter(request, response);
			}
			else {
				currentPosition++;
				// 取出additionalFilters中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() + "'");
				}
				//因为VirtualFilterChain实现了FilterChain接口,所以这里
                //利用this把请求又转给了自己,相当于递归,通过currentPosition的递增会
                 //遍历执行完additionalFilters中的全部过滤器。
				nextFilter.doFilter(request, response, this);
			}
		}
	}
}

所以真正处理请求的其实是FilterChainProxy中维护的 SecurityFilterChain ,再更新下上边说的请求处理路径

1.DelegatingFilterProxy ----->2.FilterChainProxy ---->3.SecurityFilterChain

6.2 SecurityFilterChain

继续来看下SecurityFilterChain的源码

SecurityFilterChain是一个接口,描述了一个过滤器链对象,一个过滤器链中会包含多个过滤器

public interface SecurityFilterChain {

    // 用来判断这个过滤器链对象是否适用于当前请求,
    // 大部分情况一个应用中也只会有一个SecurityFilterChain对象
	boolean matches(HttpServletRequest request);
	// 返回此过滤器链对象种所有的过滤器
	List<Filter> getFilters();
}

这个接口中的这俩方法会在6.1节描述的FilterChainProxy#getFilters方法中被调用。

接下来看下这个接口的实现类,spring security仅提供了一个实现类DefaultSecurityFilterChain

public final class DefaultSecurityFilterChain implements SecurityFilterChain {
	private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
	//用来对请求进行匹配的匹配器
    private final RequestMatcher requestMatcher;
    //存储此SecurityFilterChain对象中持有的过滤器
	private final List<Filter> filters;

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}

	public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<Filter> filters) {
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters);
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList<>(filters);
	}

	public RequestMatcher getRequestMatcher() {
		return requestMatcher;
	}

	public List<Filter> getFilters() {
		return filters;
	}

	public boolean matches(HttpServletRequest request) {
		return requestMatcher.matches(request);
	}

	@Override
	public String toString() {
		return "[ " + requestMatcher + ", " + filters + "]";
	}
}

注意这个实现类DefaultSecurityFilterChain 中的filters属性,这个属性中存放着过滤器,在FilterChainProxy#getFilters方法中获取到的过滤器集合就是这个filters。

所以请求的处理路径其实是这样的

1.DelegatingFilterProxy ----->2.FilterChainProxy ---->3.SecurityFilterChain(实际就是DefaultSecurityFilterChain) ---->4.filters属性中的多个过滤器依次应用到当前请求上

filters中的多个过滤器各自都有不同的功能,有处理登录的,有处理权限的。所以有必要看下DefaultSecurityFilterChain对象是什么时候创建出来的。

七、 DefaultSecurityFilterChain是如何构建出来的

上边第5节提到了WebSecurity这个构建器会构建出FilterChainProxy对象,在它的performBuild方法中会遍历

执行 securityFilterChainBuilders中所有的构建器的build方法来构造出多个SecurityFilterChain对象,

这个securityFilterChainBuilders是WebSecurity 中的一个属性,可以看到它其实是一个SecurityBuilder的集合,

WebSecurity部分源码

private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>();

// WebSecurity中提供了一个addSecurityFilterChainBuilder方法用来让调用者给自己添加过滤器链的构建器
// 这个方法会被谁调用后边再详细讲 第9.2节
public WebSecurity addSecurityFilterChainBuilder(
    SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) {
    this.securityFilterChainBuilders.add(securityFilterChainBuilder);
    return this;
}

如果我们没有做特殊的配置,这个集合中其实只会有一个元素,HttpSecurity,这个构建器是用来创建DefaultSecurityFilterChain的。

接下来详细看下HttpSecurity 这个类是如何构建出过滤器链对象SecurityFilterChain的

7.1 HttpSecurity分析

它实现了SecurityBuilder,根据SecurityBuilder的继承体系知道它一定会有一个performBuild方法

public final class HttpSecurity extends
		AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
		implements SecurityBuilder<DefaultSecurityFilterChain>,
HttpSecurityBuilder<HttpSecurity> {
    private final RequestMatcherConfigurer requestMatcherConfigurer;
	// 这个filters中的过滤器将来会被添加到最终创建出的DefaultSecurityFilterChain对象中
    private List<Filter> filters = new ArrayList<>();
	private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
    //这个属性用来对filters中的过滤器进行排序,决定了最终我们的请求被filters中过滤器处理的顺序
	private FilterComparator comparator = new FilterComparator();
    
    /* 这个performBuild方法先对filters做了排序,然后直接创建了一个DefaultSecurityFilterChain
       对象返回,简单直接。
       因为HttpSecurity是一个SecurityBuilder,
       所以它也可以添加配置器SecurityConfigurer,也会有build方法去执行所有配置器来对自己进行配置,
       最后才会去调performBuild方法构造出DefaultSecurityFilterChain对象。
    */
    @Override
	protected DefaultSecurityFilterChain performBuild() throws Exception {
		//利用comparator对filters做排序,从这里可以看出这个对象决定了最终的过滤器顺序
         Collections.sort(filters, comparator);
		return new DefaultSecurityFilterChain(requestMatcher, filters);
	}
    
    /*
    它还提供了addFilterAfter,addFilterBefore,addFilter等给其filters中按指定位置添加用户
    自定义过滤器的方法,我们只需要看一个就行
    */
    public HttpSecurity addFilterBefore(Filter filter,
			Class<? extends Filter> beforeFilter) {
        //把要添加的过滤器的class文件注册到comparator中,这个要具体看下comparator的源码才能理解
		comparator.registerBefore(filter.getClass(), beforeFilter);
	   //添加到属性filters中
        return addFilter(filter);
	}
    
    // 这就是addFilterBefore中调用的方法
    public HttpSecurity addFilter(Filter filter) {
		Class<? extends Filter> filterClass = filter.getClass();
		//先检查有没有在comparator中注册,没注册就抛异常,因为需要在comparator中决定顺序
        if (!comparator.isRegistered(filterClass)) {
			throw new IllegalArgumentException(
					"The Filter class "
							+ filterClass.getName()
							+ " does not have a registered order and cannot be added without a specified order. Consider using addFilterBefore or addFilterAfter instead.");
		}
         // 添加到属性filters中
		this.filters.add(filter);
		return this;
	}
}

上边提到了FilterComparator 用来决定过滤器的顺序,我们来看下这个类的源码

final class FilterComparator implements Comparator<Filter>, Serializable {
	private static final int INITIAL_ORDER = 100;
	private static final int ORDER_STEP = 100;
    //这个map用来注册过滤器,key是过滤器的class,value是一个int型的顺序
	private final Map<String, Integer> filterToOrder = new HashMap<>();

	FilterComparator() {
		Step order = new Step(INITIAL_ORDER, ORDER_STEP);
		put(ChannelProcessingFilter.class, order.next());
		put(ConcurrentSessionFilter.class, order.next());
		put(WebAsyncManagerIntegrationFilter.class, order.next());
		put(SecurityContextPersistenceFilter.class, order.next());
		put(HeaderWriterFilter.class, order.next());
		put(CorsFilter.class, order.next());
		put(CsrfFilter.class, order.next());
		put(LogoutFilter.class, order.next());
		...省略其他过滤器的注册,方式都一样
	}

    // 这个类实现了Comparator接口,在外边使用它对filters进行排序时会调用这个方法
	public int compare(Filter lhs, Filter rhs) {
	    //根据过滤器的class从上边的map filterToOrder中获取注册时指定的顺序,然后进行排序
        Integer left = getOrder(lhs.getClass());
		Integer right = getOrder(rhs.getClass());
		return left - right;
	}
}

这样最终就会得到一个有特定顺序的filters的DefaultSecurityFilterChain对象。

八、总结一下自动配置的过程

先是在SecurityFilterAutoConfiguration中给容器中加入了一个DelegatingFilterProxyRegistrationBean ,这个bean最后会给应用中注册一个DelegatingFilterProxy,这个代理过滤器会从容器中找出名称为springSecurityFilterChain的过滤器来执行它;

而这个springSecurityFilterChain是在WebSecurityConfiguration中由springSecurityFilterChain这个方法加到spring容器中的,在这个方法中会调用构建器WebSecurity.build方法创建出一个FilterChainProxy过滤器放到spring容器中;

当执行到WebSecurity.performBuild方法,会先调用SecurityFilterChain的构建器HttpSecurity的build方法构建出DefaultSecurityFilterChain对象,该对象内部会持有多个过滤器,然后把该对象作为参数创建出FilterChainProxy 对象返回。

可以看到这个过程中WebSecurity 是一个很关键的角色,它创建了DefaultSecurityFilterChainFilterChainProxy

那么还有一个问题就是WebSecurityConfiguration中的属性 WebSecurity 是在什么时候创建出来的

九、WebSecurity是什么时候创建出来的

WebSecurityConfiguration中有一个set方法,这个方法在3.1节时简单介绍了下,这里再详细看下

/*
在方法参数上利用spring注入的方式从容器中获取了WebSecurityConfigurer的bean注入到参数webSecurityConfigurers中,从名字就可以看出WebSecurityConfigurer就是用来配置WebSecurity构建器的
这个方法就是用来创建WebSecurity对象的
*/
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
        //处理器先不管,简单看这句就是new了一个WebSecurity对象。
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}
		//对上边注入的webSecurityConfigurers进行排序
		Collections.sort(webSecurityConfigurers, 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) {
            //这里就是给webSecurity循环应用从容器中获取到的WebSecurityConfigurer,
            // 对这个webSecurity对象进行配置
            //配置完成后此类中的springSecurityFilterChain方法就会用这个构建器来创建Filter对象
		   //这个apply方法就是把配置器configurer添加到webSecurity中,在webSecurity.dobuild方法
            //中就会使用配置器对webSecurity进行配置。
            //apply方法就是把这个配置器添加到webSecurity的内部的一个集合属性中,
            //在执行webSecurity.build方法时这个集合属性中的配置器的方法(init,config)就会被执行
            webSecurity.apply(webSecurityConfigurer);
		}
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

那么重点就落到了WebSecurityConfigurer上

9.1 WebSecurityConfigurer

public interface WebSecurityConfigurer<T extends SecurityBuilder<Filter>> extends
		SecurityConfigurer<Filter, T> {

}

这是一个空接口,它继承了SecurityConfigurer,所以它就会有init和config两个方法。

所以在使用spring security时需要给容器中注入这个接口的实现类来对webSecurity进行配置。

为了方便配置,spring security已经提供好了一个实现类WebSecurityConfigurerAdapter

这就是为什么在springboot中配置spring security时我们总是新建一个类继承这个类,然后加入到容器中,类似这样

@Configuration
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    
    ...在这里进行其他的一些配置
}

9.2 WebSecurityConfigurerAdapter

接下来分析下这个类的源码,这个类对WebSecurity进行配置的起点就是它的init方法,

当在WebSecurityConfiguration#springSecurityFilterChain()方法中执行webSecurity.build()方法时,就会执行到WebSecurity的父类AbstractConfiguredSecurityBuilder#doBuild方法,

protected final O doBuild() throws Exception {
    synchronized (configurers) {
        buildState = BuildState.INITIALIZING;

        beforeInit();
        init();//调用自己的inti方法去执行配置器的init方法

        buildState = BuildState.CONFIGURING;

        beforeConfigure();
        configure();

        buildState = BuildState.BUILDING;

        O result = performBuild();

        buildState = BuildState.BUILT;

        return result;
    }
}

private void init() throws Exception {
    //获取所有的配置器,当由webSecurity.build()触发到这里时就会获取到WebSecurityConfigurerAdapter
    Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

    for (SecurityConfigurer<O, B> configurer : configurers) {
        //执行configurer的init方法
        configurer.init((B) this);
    }

    for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
        configurer.init((B) this);
    }
}

所以接下来看下WebSecurityConfigurerAdapter#init方法

public void init(final WebSecurity web) throws Exception {
    //获取一个HttpSecurity对象,它是用来创建DefaultSecurityFilterChain的
    final HttpSecurity http = getHttp();
    //添加到WebSecurity对象中,第7节时提到过这个方法
    //后边的postBuildAction先不管,我们需要先从整体上感知一个框架再去抠细节。
    web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
        public void run() {
            FilterSecurityInterceptor securityInterceptor = http
                .getSharedObject(FilterSecurityInterceptor.class);
            web.securityInterceptor(securityInterceptor);
        }
    });
}

总结一下就是这个init方法给WebSecurity添加了一个用来创建DefaultSecurityFilterChain的HttpSecurity对象。

接着我们详细看下这个getHttp方法,这个方法返回的HttpSecurity对象决定了DefaultSecurityFilterChain中的过滤器都有哪些。

getHttp方法

protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}
		//发布事件相关的先不看
		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
		//获取一个认证管理器
		AuthenticationManager authenticationManager = authenticationManager();
        // 把这个认证管理器设置成全局的认证管理器。
        // 在登录过滤器中对账号密码进行认证时会先使用局部的认证管理器进行认证,
        //如不能认证最后会再使用全局认证管理器(parent属性)进行一次认证,
        //后边写AuthenticationManager时会具体写这部分
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		authenticationBuilder.authenticationEventPublisher(eventPublisher);
		Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();
		//这里new 了一个HttpSecurity对象
		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		if (!disableDefaults) {
            //如果没有禁用默认配置,(大部分情况都不会禁用),
            //会在这里给http进行一些默认配置
			http
				.csrf().and()
				.addFilter(new WebAsyncManagerIntegrationFilter())
				.exceptionHandling().and()
				.headers().and()
				.sessionManagement().and()//session相关的过滤器添加
				.securityContext().and()//securityContext相关的过滤器的添加
				.requestCache().and()
				.anonymous().and()
				.servletApi().and()
                  //默认的登录页面对应的过滤器的添加,spring security默认的那个登录页面是通过过滤器
                  //实现的,具体可以看下这个config中做了哪些事情。
				.apply(new DefaultLoginPageConfigurer<>()).and()
				.logout();
			// @formatter:on
			ClassLoader classLoader = this.context.getClassLoader();
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);

			for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
			  //如果有的话,应用一些默认的配置器。
                http.apply(configurer);
			}
		}
        //这个configure方法可以被子类覆盖来实现对http对象做自定义配置,
        // 我们自己继承WebSecurityConfigurerAdapter时一般都会实现这个方法配置http对象
        // 当然这个方法在WebSecurityConfigurerAdapter中有一些比较重要的默认配置,接着看下这个方法
		configure(http);
		return http;
}

protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()//这里指定了所有的请求都需要登录才能访问
				.and()
             //这里添加了表单登录的过滤器
			.formLogin().and()
             //这里添加了httpBasic认证的过滤器
			.httpBasic();
	}
所以重点来了:如果我们自己在配置类中重写了这个configure方法,你必须手动的去用formLogin方法添加表单登录
的过滤器,否则我们的应用中就没有表单登录的功能,
这是一个深坑,初学者使用spring security时往往直接重写这个configure但又没有调用formLogin,造成系统中
没有登录功能所有请求都是401或者403

到这里再结合第8节的总结,我们对在springboot中整个security是如何配置起来的就有一个比较全面的了解了,

我们要习惯这种用SecurityBuilder去创建对象,在合适的时机去添加SecurityConfigurer,

SecurityConfigurer去对构建器做配置这种模式。像上边的.formLogin()方法就给HttpSecurity这个构建器中添加了一个FormLoginConfigurer,而这个config就是用来做表单登录相关的配置的。

当你习惯了这种模式再去看spring security的源码就会容易许多。

标签:spring,public,源码,filters,过滤器,new,security,方法,class
From: https://www.cnblogs.com/chengxuxiaoyuan/p/17300575.html

相关文章

  • 源码共读 | 为 vite 项目自动添加 eslint 和 prettier
    前言Vite是一个用于现代JavaScript应用程序的快速、轻量级的构建工具,其设计目的是易于使用和适用于大型项目。Vite-pretty-lint是一个插件,可以在基于Vite的项目中安装和配置,以便在编写代码时能够自动对代码进行格式化和检查代码。这可以帮助开发人员在开发过程的早期捕获格......
  • Spring
    Spring核心概念IoC(控制反转)(InversionofControl)概念使用对象时,由主动new产生对象转换为由外部提供对象,在此过程中对象创建控制权由程序转移到外部,此思想称为控制反转。Spring提供了一个容器,称为Ioc容器,用来充当Ioc思想中的“外部”。IoC容器负责对象的创建、初始化等......
  • Spring Boot 3.0正式发布及新特性解读
    官网新特性解读:Springboot3.0新特性解读SpringBoot最新最全的实战代码已上传Github:SpringBoot3实战SpringBoot3.0简介**Java17+Spring6+Maven3.5/Gradle7.3**JDK要求最低版本Java17SpringBoot3底层默认依赖Spring6支持JakartaEE10,由于JavaEE已经变更为Jaka......
  • Spring Cloud Admin添加微信通知
    SpringCloud发送微信消息推送参考https://blog.csdn.net/qq_44697754/article/details/128035736。  SpringCloudAdmin要增加微信通知,需要继承AbstractStatusChangeNotifier类,在doNotify方法按照模板发送消息。 AdminServe添加依赖:<dependency><groupId>common......
  • Idea点击Run或者Debug无法启动项目_调试按钮按下以后变灰色_一会又恢复成绿色_但项目
    这个现象很烦人,点击了无数次了,就是项目启动不起来,很郁闷后来终于弄明白了,是这里,点击settings,然后找到这个runner这里,然后左上角这个delegateIDE...这个把勾去掉,去掉就可以了. 可以看到去掉以后,然后再点击运行可以看到,就已经显示正在运行中了. 终于弄好~......
  • ava: 程序包com.alibaba.nacos.api.common不存在_RuoYi-Cloud-Plus-master_jar包不存
    来看看原因吧,jar包是存在的,但是就是在idea中引用不到,来看看怎么回事: 原来就是这个包找不到,但是从下面看是有的: 但是注意,这里的com.alibaba.nacos.api...原来可不是这样的,这个是我后来修改过的,原来是只有com.alibaba.nacos.common,而引用的是com.alibaba.nacos.api.commo......
  • ruoyi-cloud微服务版启动过程报错_20230320版_ Verion 9 of Highlight.js has reached
      Verion9ofHighlight.jshasreachedEOL. Itwillnolonger报错: 这里修改成10.7.3版本D:\2023\qdBigData\RuoYi-Cloud-master\ruoyi-ui>npminstall--registry=https://registry.npm.taobao.org然后到对应目录,再去执行编译去看看.不报错了 >npmrundev然后执行看......
  • SpringBoot中日志的使用
    springboot默认就是使用SLF4J作为日志门面,logback作为日志实现来记录日志的文章目录1.SpringBoot中的日志设计2.SpringBoot日志使用1.SpringBoot中的日志设计springboot中的日志<dependency> <artifactId>spring-boot-starter-logging</artifactId> <groupId>org.springfr......
  • Java SpringBoot Bean InitializingBean
    Spring中有两种类型的Bean,一种是普通Bean,另一种是工厂Bean,即FactoryBean。工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,其返回的是该工厂Bean的getObject方法所返回的对象。Spring初始化bean有两种方式:实现InitializingBean接口,实现afterPropertiesSet方法。(比通过反射......
  • Java SpringBoot Test 单元测试中包括多线程时,没跑完就结束了
    如何阻止JavaSpringBootTest单元测试中包括多线程时,没跑完就结束了使用CountDownLatchCountDownLatch、CyclicBarrier使用区别多线程ThreadPoolTaskExecutor应用JavaBasePooledObjectFactory对象池化技术@SpringBootTestpublicclassPoolTest{@Testvoid......