Spring Security原理分析【1】——整体布局切入

Spring Security 是Spring家族中基于JavaEE的企业Web应用程序的安全服务框架。准确而言是基于JavaEE中Servlet规范的Filter机制。

根据Servlet规范:一个客户端请求Request在Servlet容器中需要经过Filter Chain中一些列Filter处理后才会获取到Web资源,而且响应Response也需要再次经过Filter Chain中的Filter处理后才能返回给客户端。Spring基于该Servlet规范,在Filter中接入安全机制来保证Web资源的安全。

在Spring中,Filter对应的Bean为GenericFilterBean。它是一个抽象类,具体类为DelegatingFilterProxy,该类是一个委托类。

public class DelegatingFilterProxy extends GenericFilterBean {
    ...
    // Web上下文
    @Nullable
    private WebApplicationContext webApplicationContext;

     // 委托目标类名称
    @Nullable
    private String targetBeanName;
    // 是否管理Filter的生命周期
    private boolean targetFilterLifecycle = false;

     // 委托目标类实例(Filter)
    @Nullable
    private volatile Filter delegate;
    ...
}

核心方法doFilter简化后如下:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
  throws ServletException, IOException {
  // delegate支持懒加载
  Filter delegateToUse = this.delegate;
  // invokeDelegate 调用委托目标的doFilter
  delegate.doFilter(request, response, filterChain);
}

正常情况下Servlet的Filter需要配置在web.xml中,这样Servlet容器初始化时就能找到并管理其生命周期,DelegatingFilterProxy则是Spring中用来找到那些不注册在web.xml中的Filter,然后交给Servlet去处理。

DelegatingFilterProxy是通过WebApplicationContext从IOC中根据targetBeanName获取已注册的Filter Bean。Spring Security基于Spring,因此也是利用该机制来完成Filter的注册的,其targetBeanName="springSecurityFilterChain"。

在基于web.xml类型的Web应用中,需要配置:

<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>

基于JavaConfig类型的Web应用中,则需要配置:

@Bean
public FilterRegistrationBean springSecurityFilterChain(ServletContext servletContext) {
  DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy("springSecurityFilterChain");
  return new FilterRegistrationBean(springSecurityFilterChain);
}

springSecurityFilterChain这只是BeanName,其具体类为:FilterChainProxy,其doFilter如下:

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

前面也说了一个客户端请求需要经过2次Filter,而整个请求是单条链,因此正常情况下只需要一次就可以了。Filter的核心为doFilter,在FilterChainProxy中抽象为doFilterInternal进行了隔离分层 

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);
	// 如果未获取到,则说明没有额外的过滤器注册,正常走Servlet的Filter流程
	if (filters == null || filters.size() == 0) {	
		fwRequest.reset();
		chain.doFilter(fwRequest, fwResponse);
		return;
	}
	// 如果获取到,则走Spring Security的过滤器执行流程,构建了VirtualFilterChain
	VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
	// 虚拟的过滤器链执行
	vfc.doFilter(fwRequest, fwResponse);
}

这里的getFilters很重要,决定了Spring Security到底执行了什么过滤器

private List<Filter> getFilters(HttpServletRequest request) {
	// 对SecurityFilterChain逐个匹配HttpServletRequest
	for (SecurityFilterChain chain : filterChains) {
		if (chain.matches(request)) {
			// 每条链是多个Filter,但是最终只返回首个匹配的链
			return chain.getFilters();
		}
	}
	return null;
}

SecurityFilterChain 是配置到FilterChainProxy中的,配置过程很复杂,这里先不说,最终配置好的SecurityFilterChain为一个集合:filterChains。而SecurityFilterChain#matches则决定了请求流程,这是一个单向开关,多个过滤器链最终只能执行一条。单条过滤器链抽象为VirtualFilterChain,该类是一个经典的变形责任链模式。 

其实到这里,一个正常的Servlet容器已经将Spring Security容纳进去了:

 

posted @ 2022-05-01 21:06  残城碎梦  阅读(231)  评论(0编辑  收藏  举报