Spring Security作为一个安全框架,主要有登录、认证等功能,而要实现这一系列的功能,在一个基于Servlet的应用上,当然会从Filter上入手。这篇博客主要就是根据官网文档 讲解Spring Security怎么通过Filter构建这样一个安全拦截的功能的。

首先看看以下这幅图,这个流程用过spring mvc的都很熟悉,这就是filter的一个过滤流程,spring mvc中通常都会有一个Filterchain包含了一个Filter列表,对于每个拦截的请求依次过滤

Spring security url白名单_ide

了解了spring mvc的Filter流程之后,再来看一看DelegatingFilterProxy。

DelegatingFilterProxy有啥用呢?

public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
   Assert.hasText(targetBeanName, "Target Filter bean name must not be null or empty");
   this.setTargetBeanName(targetBeanName);
   this.webApplicationContext = wac;
   if (wac != null) {
      this.setEnvironment(wac.getEnvironment());
   }
}

DelegatingFilterProxy也是spring mvc中的一个Filter,根据名字就可以看出是个委托类,看这个构造方法,接受一个targetBeanName,和一个WebApplicationContext 。

它的作用就是去Spring容器中查找一个Bean名称是targetBeanName的一个Filter并调用。

Spring security url白名单_java_02

如图,真正的逻辑还是在Bean中的这个Filter中。接下来再来看看Spring Security的具体代码。

先看Spring Mvc中的

如下,在项目中实现AbstractSecurityWebApplicationInitializer 就可以调用到onStartup,这个是根据SPI实现的,不了解的跳过也行,只需要知道实现了这个类就会调用onStartup方法,或者去Spring mvc的官网看看,就在开头前几篇。

在onStartup方法中看到,去注册了一个DelegatingFilterProxy

@Order(2)
public class MessageSecurityWebApplicationInitializer extends
		AbstractSecurityWebApplicationInitializer {
}


//AbstractSecurityWebApplicationInitializer 中
public final void onStartup(ServletContext servletContext) {
		beforeSpringSecurityFilterChain(servletContext);
		if (this.configurationClasses != null) {
			AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
			rootAppContext.register(this.configurationClasses);
			servletContext.addListener(new ContextLoaderListener(rootAppContext));
		}
		if (enableHttpSessionEventPublisher()) {
			servletContext.addListener(
					"org.springframework.security.web.session.HttpSessionEventPublisher");
		}
		servletContext.setSessionTrackingModes(getSessionTrackingModes());
		//重点,关键部分
		//注册一个DelegatingFilterProxy
		insertSpringSecurityFilterChain(servletContext);
		afterSpringSecurityFilterChain(servletContext);
	}

再看看spring boot的实现

在spring-boot-autoconfigure中的security包下SecurityFilterAutoConfiguration中,这个怎么执行到的就不用多说了。

spring boot会自动注册一个DelegatingFilterProxyRegistrationBean,而这个就是为了提供DelegatingFilterProxy

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


   //DelegatingFilterProxyRegistrationBean 中

	@Override
	public DelegatingFilterProxy getFilter() {
		return new DelegatingFilterProxy(this.targetBeanName, getWebApplicationContext()) {

			@Override
			protected void initFilterBean() throws ServletException {
				// Don't initialize filter bean on init()
			}

		};
	}

了解了DelegatingFilterProxy的来龙去脉之后,就会知道,Spring Security注册Filter就是向Spring的容器中注入一个相同名称的Filter的Bean就可以执行过滤了,这个Bean的名称是springSecurityFilterChain,在AbstractSecurityWebApplicationInitializer中可以看到

现在就看看Spring Security中是在哪将这个Filter放入Spring容器中的。

而在spring boot中使用过Spring Security的都知道,只要引入了Spring Security的包,默认就会拦截,所以肯定有地方已经默认提供了这个Filter

在spring-boot-autoconfigure的security包下SecurityAutoConfiguration中,@Import了三个类

@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
      SecurityDataConfiguration.class })

重点看WebSecurityEnablerConfiguration,这个类上有个注解@EnableWebSecurity

这个注解就是Spring Security生效的关键。

所以为什么我们使用Spring Security的基操都如下

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	// @formatter:off
	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http
				.authorizeRequests(authorize -> authorize
					.antMatchers("/css/**", "/index").permitAll()
					.antMatchers("/user/**").authenticated()
				)
				.formLogin(formLogin -> formLogin
					.loginPage("/login")
					.failureUrl("/login-error")
				);


	}
	// @formatter:on

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails userDetails = User.withDefaultPasswordEncoder()
				.username("user")
				.password("password")
				.roles("USER")
				.build();
		return new InMemoryUserDetailsManager(userDetails);
	}
}

这个熟悉的WebSecurityConfigurerAdapter 先放一边,等下再说。

而@EnableWebSecurity注解又是怎样生成Filter呢?

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.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了四个类,关键就是WebSecurityConfiguration。在这个类中生成了filter

@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {

		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		boolean hasFilterChain = !securityFilterChains.isEmpty();
		if (hasConfigurers && hasFilterChain) {
			throw new IllegalStateException(
					"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain." +
							"Please select just one.");
		}
		//如果没有WebSecurityConfigurerAdapter配置,并且没有SecurityFilterChain,那么生成一个默认的
		if (!hasConfigurers && !hasFilterChain) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		// spring容器中的SecurityFilterChain列表,这个一般是安全命名空间,也就是xml生成的
		for (SecurityFilterChain securityFilterChain : securityFilterChains) {
			webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
			for (Filter filter : securityFilterChain.getFilters()) {
				if (filter instanceof FilterSecurityInterceptor) {
					webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
					break;
				}
			}
		}
		//构建Filter
		return webSecurity.build();
	}

到了这一步之后,再接着看下一幅图

首先,先别把这个图中的SecurityFilterChain和之前说的Bean名称springSecurityFilterChain弄混

这个FilterChainProxy就是上面代码中springSecurityFilterChain()方法返回的Filter,按照图中的意思就是,这个Filter实际上还会再代理一层,对SecurityFilterChain进行代理

Spring security url白名单_java_03

 那么再来看看FilterChainProxy的产生,来看一看这一行代码的具体内容,这个webSecurity就是WebSecurity类

return webSecurity.build();

所以它的具体实现,就是如下

@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
						+ "Typically this is done by exposing a SecurityFilterChain bean "
						+ "or 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));
		}

		//根据securityFilterChainBuilders列表中的构建器 构建SecurityFilterChain
		// 至少有一个HttpSecurity  在WebSecurityConfigurerAdapter的init方法中被加入
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}

		//新建FilterChainProxy,将所有的SecurityFilterChain放入其中
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);

		//设置httpFirewall和requestRejectedHandler
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		if (requestRejectedHandler != null) {
			filterChainProxy.setRequestRejectedHandler(requestRejectedHandler);
		}
		//验证
		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;
	}

可以看到,最后new了一个FilterChainProxy ,并且放入SecurityFilterChain列表,再看看那幅图,是不是流程基本完全一样

所以再看下一幅图

这里也要强调一下,虽然上面代码中传入的是一个SecurityFilterChain列表,但是这个SecurityFilterChain列表下一幅图才说,这幅图里面的Security Filter列表是一个SecurityFilterChain类中包含的多个Security Filter,不要弄混了

Spring security url白名单_spring_04

所以到了这里,Filter执行的逻辑就是,通过DelegatingFilterProxy代理,代理到FilterChainProxy,而FilterChainProxy又代理到SecurityFilterChain,但是实际上真正的执行逻辑的Filter,就是SecurityFilterChain中的这一系列的Security Filter列表。

那接下来就看看这一系列的Security Filter又是怎么生成的。

这里就可以说到WebSecurityConfigurerAdapter了,因为我们自定义的配置都是通过这个去配置的。

但是WebSecurityConfigurerAdapter是怎么关联进来的呢?这要从构建FilterChainProxy的过程中说起。

protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;
            // 初始化之前,默认无操作
			beforeInit();

			// 执行SecurityConfigurer 的init方法
			init();

			buildState = BuildState.CONFIGURING;

			//HttpSecurity有覆盖方法,WebSecurity没有
			beforeConfigure();

			//执行执行SecurityConfigurer 的configure方法
			configure();

			buildState = BuildState.BUILDING;

			// 真正的构建
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

这个是FilterChainProxy构建过程中父类AbstractConfiguredSecurityBuilder中的逻辑,AbstractConfiguredSecurityBuilder的实现类最主要的有三个,HttpSecurity、WebSecurity、AuthenticationManagerBuilder

目前在说的一直都是WebSecurity的。说这个主要是捋一下现在的类的关系。

在上面的代码中,和WebSecurityConfigurerAdapter有关的就是init()方法和configure()方法

先看init()方法

@SuppressWarnings("unchecked")
	private void init() throws Exception {

		// HttpSecurity
		 // CsrfConfigurer、ExceptionHandlingConfigurer、HeadersConfigurer、SecurityContextConfigurer
		 //ServletApiConfigurer、ExpressionUrlAuthorizationConfigurer
		// 以下是有init方法的
		//SessionManagementConfigurer、RequestCacheConfigurer、AnonymousConfigurer
		//DefaultLoginPageConfigurer、LogoutConfigurer、FormLoginConfigurer


		//WebSecurity
        //WebSecurityConfigurerAdapter
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

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

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

这个SecurityConfigurer就是WebSecurityConfigurerAdapter的父类,而WebSecurityConfigurerAdapter是怎么放入进来的?这个就回头去看WebSecurityConfiguration类,大致就是Spring注入进来的,然后添加到WebSecurity中,具体逻辑就不展示了,很简单。

所以当前执行的就是WebSecurityConfigurerAdapter的init方法

//初始化
	//主要是加载自定义的配置
	public void init(final WebSecurity web) throws Exception {

		//构建HttpSecurity和AuthenticationManager
		final HttpSecurity http = getHttp();

		//将HttpSecurity放入WebSecurity的构造器列表中
		web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
			//在构造器执行后执行
			FilterSecurityInterceptor securityInterceptor = http
					.getSharedObject(FilterSecurityInterceptor.class);
			web.securityInterceptor(securityInterceptor);
		});
	}

在这里生成了一个HttpSecurity,并放入到当前的WebSecurity中 ,这一点非常关键。

getHttp()代码就不贴了,总之就是我们自定义HttpSecurity,如果没自定义,那就用它默认构造的那一套。

所以这里主要就是构建这个HttpSecurity。

接下来那个configure()方法就没啥好说的了,就是给我们提供了一个自定义WebSecurity的机会

最后就到了FilterChainProxy的构建方法了,到现在为止并没有哪里生成SecurityFilter的,以上主要是为了说明WebSecurityConfigurerAdapter怎么工作的,以及生成了这个HttpSecurity。

再看FilterChainProxy的构建方法performBuild()

//根据securityFilterChainBuilders列表中的构建器 构建SecurityFilterChain
		// 至少有一个HttpSecurity  在WebSecurityConfigurerAdapter的init方法中被加入
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}

当前的performBuild()是WebSecurity在build,这里有个securityFilterChainBuilder.build()是WebSecurity中的HttpSecurity进行build构建。

逻辑和WebSecurity类似,他们是同一个抽象父类。

WebSecurity中的是一个或多个WebSecurityConfigurerAdapter,HttpSecurity中的是在WebSecurityConfigurerAdapter中自定义或默认的多个SecurityConfigurer列表,在configure()方法中被构建成了一个个的Security Filter。这些Filter执行真正的拦截逻辑。

以下列出了Spring Security中所有自带的Filter的先后顺序。

ChannelProcessingFilter

WebAsyncManagerIntegrationFilter

SecurityContextPersistenceFilter

HeaderWriterFilter

CorsFilter

CsrfFilter

LogoutFilter

OAuth2AuthorizationRequestRedirectFilter

Saml2WebSsoAuthenticationRequestFilter

X509AuthenticationFilter

AbstractPreAuthenticatedProcessingFilter

CasAuthenticationFilter

OAuth2LoginAuthenticationFilter

Saml2WebSsoAuthenticationFilter

UsernamePasswordAuthenticationFilter

OpenIDAuthenticationFilter

DefaultLoginPageGeneratingFilter

DefaultLogoutPageGeneratingFilter

ConcurrentSessionFilter

DigestAuthenticationFilter

BearerTokenAuthenticationFilter

BasicAuthenticationFilter

RequestCacheAwareFilter

SecurityContextHolderAwareRequestFilter

JaasApiIntegrationFilter

RememberMeAuthenticationFilter

AnonymousAuthenticationFilter

OAuth2AuthorizationCodeGrantFilter

SessionManagementFilter

ExceptionTranslationFilter

FilterSecurityInterceptor

SwitchUserFilter

到此就和之前那幅图相吻合了。

接下来对上面那幅图稍微扩展了一下,变成了多个SecurityFilterChain

Spring security url白名单_java_05

这只需要构建多个WebSecurityConfigurerAdapter就可以了,多个WebSecurityConfigurerAdapter会生成多个HttpSecurity,并最后构建成多个SecurityFilterChain,而在FilterChainProxy中是这样的

private List<Filter> getFilters(HttpServletRequest request) {
		for (SecurityFilterChain chain : filterChains) {
			if (chain.matches(request)) {
				return chain.getFilters();
			}
		}

		return null;
	}

会根据路径匹配出相应的SecurityFilterChain执行。

最后再来看这个Spring Security的异常处理图

Spring security url白名单_mvc_06

这里的ExceptionTranslationFilter就是那多个Security Filter中的其中一个,在执行顺序中倒数第三。

ExceptionTranslationFilter的处理如下

执行下一个Filter的逻辑,如果捕获到AuthenticationException异常,则表示未登录,那么就清空SecurityContextHolder,把HttpServletRequest保存到RequestCache中,这个用来在在登录成功后重新回到这个请求。最后,通过AuthenticationEntryPoint重定向到登录页。

如果捕获到AccessDeniedException异常,则调用AccessDeniedHandler处理

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		try {
			chain.doFilter(request, response);

			logger.debug("Chain processed normally");
		}
		catch (IOException ex) {
			throw ex;
		}
		catch (Exception ex) {

			//获取出AuthenticationException和AccessDeniedException两种异常进行处理

			// Try to extract a SpringSecurityException from the stacktrace
			Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
			RuntimeException ase = (AuthenticationException) throwableAnalyzer
					.getFirstThrowableOfType(AuthenticationException.class, causeChain);

			if (ase == null) {
				ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
						AccessDeniedException.class, causeChain);
			}

			if (ase != null) {
				if (response.isCommitted()) {
					throw new ServletException("Unable to handle the Spring Security Exception because the response is already committed.", ex);
				}

				//处理SpringSecurity异常
				handleSpringSecurityException(request, response, chain, ase);
			}
			else {
				//其他的异常继续往上抛
				// Rethrow ServletExceptions and RuntimeExceptions as-is
				if (ex instanceof ServletException) {
					throw (ServletException) ex;
				}
				else if (ex instanceof RuntimeException) {
					throw (RuntimeException) ex;
				}

				// Wrap other Exceptions. This shouldn't actually happen
				// as we've already covered all the possibilities for doFilter
				throw new RuntimeException(ex);
			}
		}
	}

Spring Security整体构建大致如上,博客基本都是参照官方文档来写的,所以下一篇就是认证的具体流程