spring-security(十六)Filter配置原理

前言:
  spring security最常见的应用场景还是基于http请求和servlet API的web应用,接下来的几个章节我们将重点探讨下spring security是如何在web应用中实现认证和访问控制的。通过前面章节的讲述,我们已经了解到,在web应用中spring是通过各种各样的filter来做认证和鉴权,本小节就主要讨论下spring中过滤器的几个主要组件。
1.DelegatingFilterProxy
为了使Filter起作用,我们必须明确的在web.xml中声明,或者通过servlet3.0+中追加的方法ServletContext.addFilter方法追加到servlet中,否则这个filter是会被servlet容器忽略的。在spring security中,我们的filter也是一个spring bean,也需要用到依赖注入功能来装配,而我们知道,正常情况下servlet context的创建是在application context之前的,如果我们直接把我们的filter注册到servlet中,filter中依赖注入的对象将获取不到,为了解决这个问题,spring引入了DelegatingFilterProxy类,他提供了一servlet context和application context之间的一种链接。
在web.xml中使用DelegatingFilterProxy,通常采用下面的配置代码段
<filter>
 <filter-name>myFilter</filter-name> 
 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>myFilter</filter-name> 
 <url-pattern>/*</url-pattern>
</filter-mapping>


相应的java config代码段如下:
Filter filter = new DelegatingFilterProxy("myFilter");
servletContext.addFilter("myFilter", filter)
                        .addMappingForUrlPatterns(
                         EnumSet.of(DispatcherType.FORWARD, 
                                              DispatcherType.INCLUDE,
                                              DispatcherType.REQUEST), false, "/*");

可以看到,我们实际上配置的Filter是DelegatingFilterProxy,并不是我们实际实现了Filter接口的类,当有请求满足DelegatingFilterProxy的UriPatterns时,DelegatingFilterProxy类会首先判断实际的Filter类是否已经配置过,如果没有将根据配置的bean名称从spring context中获取对应的bean,然后将处理委托给我们真实的Filter类。这样我们真实的Filter类就可以利用spring bean生命周期方法以及灵活的依赖注入配置,需要注意的是我们的Filter类注入到spring context中的bean名称必须和传入到DelegatingFilterProxy中的名字一致。DelegatingFilterProxy中处理请求的代码段如下:
@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) {
				if (this.delegate == null) {
				WebApplicationContext wac = findWebApplicationContext();
				if (wac == null) {
				throw new IllegalStateException("No WebApplicationContext found: " +
					"no ContextLoaderListener or DispatcherServlet registered?");
					}
					this.delegate = initDelegate(wac);
				}
				delegateToUse = this.delegate;
			}
		}

		// Let the delegate perform the actual doFilter operation.
		invokeDelegate(delegateToUse, request, response, filterChain);
	}

......

protected void invokeDelegate(
			Filter delegate, ServletRequest request, ServletResponse response,FilterChain filterChain)
			throws ServletException, IOException {

		delegate.doFilter(request, response, filterChain);
	}


2.FilterChainProxy
在spring security我们通过DelegatingFilterProxy注册到servlet context中并不是一个个单独的filter,而是FilterChainProxy,一个FilterChainProxy就是一系列过滤器链的代理,里面包含很多个的过滤器链(过滤器链在spring中的统一接口是SecurityFilterChain,每一个SecurityFilterChain中都包含一个排好序的过滤器列表),当FilterChainProxy接受到DelegatingFilterProxy委托过来的请求时,就从这些过滤器链中查找到第一个满足当前请求的过滤器链,并获取这个过滤器链中的过滤器列表,然后依次执行获取到的过滤器列表。虽然理论上我们是能够通过DelegatingFilterProxy将我们需要的filter一个个都配置到web.xml或者通过servletContext.addFilter方法追加到servlet中,但是随着我们filter的追加这种方式会变的难以维护,并且这样一来filter的执行顺序将严格依赖我们追加的顺序,缺少灵活性。下面是一个例子
<bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy"> <constructor-arg>
<list>
<sec:filter-chain pattern="/restful/**" filters="
  securityContextPersistenceFilterWithASCFalse,
  basicAuthenticationFilter,
  exceptionTranslationFilter,
  filterSecurityInterceptor" />
<sec:filter-chain pattern="/**" filters="
 securityContextPersistenceFilterWithASCTrue,
 formLoginFilter, 
 exceptionTranslationFilter, 
 filterSecurityInterceptor" />
 </list>
</constructor-arg>
</bean>



在FilterChainProxy中的多个过滤器链中,我们是用第一个匹配当前请求的过滤器链对请求进行filter操作,所以越具体的URI应配置的越靠前


3.下面我们来看看在spring security中是如何把我们需要的filter注册到servlet中的
3.1首先是WebSecurity类的performBuild方法,
这个方法会把我们追加到ignoredRequests(通过ignoring()方法配置)和配置好的securityFilterChainBuilders(实际就是HttpSecurity)分别配置成SecurityFilterChain,代码片段如下:
@Override
	protected Filter performBuild() throws Exception {
		...
		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);
...

3.2配置好的FilterChainProxy返回后会在WebSecurityConfiguration中被注册到spring context,bean名称为springSecurityFilterChain
/**
	 * Creates the Spring Security Filter Chain
	 * @return
	 * @throws Exception
	 */
	@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);
		}
		return webSecurity.build();
	}

如果我们有继承WebSecurityConfigurerAdapter的配置类,就会用我们自己的。
AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME的具体值就是springSecurityFilterChain
public abstract class AbstractSecurityWebApplicationInitializer
		implements WebApplicationInitializer {

...
public static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";
...

3.3 通过DelegatingFilterProxy把名称为springSecurityFilterChain的bean注册到servlet中,在spring boot工程中这个是通过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.getFilterOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}

可以看到,当我们的context中存在名称为springSecurityFilterChain的bean后,spring boot将自动为我们注册一个DelegatingFilterProxyRegistrationBean,并将springSecurityFilterChain传递进去,DelegatingFilterProxyRegistrationBean这个bean的继承关系为
DelegatingFilterProxyRegistrationBean ->AbstractFilterRegistrationBean->RegistrationBean->ServletContextInitializer,所以在servlet启动时会出发onStartup方法,在这个方法内部,具体实现在AbstractFilterRegistrationBean类中
@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		Filter filter = getFilter();
		Assert.notNull(filter, "Filter must not be null");
		String name = getOrDeduceName(filter);
		if (!isEnabled()) {
			this.logger.info("Filter " + name + " was not registered (disabled)");
			return;
		}
		FilterRegistration.Dynamic added = servletContext.addFilter(name, filter);
		if (added == null) {
			this.logger.info("Filter " + name + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
		configure(added);
	}

可以看到,这里会获取一个filter并注册到servletContext中,具体的getFilter方法在DelegatingFilterProxyRegistrationBean中
@Override
	public Filter getFilter() {
		return new DelegatingFilterProxy(this.targetBeanName,
				getWebApplicationContext()) {

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

		};
	}

很明显,是用了一个DelegatingFilterProxy将我们传入的bean名称进行了包装。至此spring security中用到的filter就被以DelegatingFilterProxy的形式注册到了servlet。

具体我们想用的filter如 UsernamePasswordAuthenticationFilter、SecurityContextPersistenceFilter等是如何追加到HttpSecurity中,从而在3.1步骤中被加入到SecurityFilterChain中的,我们在具体讨论每一个filter时再继续讲解

4.绕过filter
在实际应用开发中,对于某些资源的访问,我们可能不想使用spring security进行保护,例如一些静态的js、html等,下面分别给出用xml和java config两种配置形式实现这个功能的例子
xml配置中,我们可以将对应URI的filter设置成none,如下
<http pattern="/static/**" security="none"/>
<http pattern="/resources/**" security="none"/>

对应的java config
@Override
	public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/resources/**", "/static/**");
	}

通过3.1小节,我们知道通过web.ignoring()配置的过滤规则,会被配置成DefaultSecurityFilterChain追加到过滤器链的列表的最前面,并且在配置DefaultSecurityFilterChain时只传入了RequestMatcher,没有传入任何filter,DefaultSecurityFilterChain构造函数如下,
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
		this(requestMatcher, Arrays.asList(filters));
	}

表示对于满足条件的uri不适用任何过滤器来处理,和xml中security="none"效果一样。
另外需要注意的一点是,采用这种配置后,任何满足条件的URI将不被spring security的过滤器处理,此时SecurityContext中将不包含任何用户信息。

猜你喜欢

转载自fengyilin.iteye.com/blog/2411127