Spring Security的过滤器
Spring Security文档的The Security Filter Chain一章指出Spring Security完全基于标准的servlet过滤器,本文结合笔者的看法对该文档进行一些补充说明。
DelegatingFilterProxy类
DelegatingFilterProxy用于代理其他的过滤器,位于spring-web.jar中,这使得Spring可以通过它方便地使用Spring容器管理的过滤器。DelegatingFilterProxy的部分代码如下所示,它继承自GenericFilterBean类,GenericFilterBean的作用与DispatcherServlet的初始化过程这篇文章中介绍的HttpServletBean相似。
public class DelegatingFilterProxy extends GenericFilterBean {
private String contextAttribute;
private WebApplicationContext webApplicationContext;
private String targetBeanName;
private boolean targetFilterLifecycle = false;
private volatile Filter delegate;
private final Object delegateMonitor = new Object();
// 省略一些代码
@Override
protected void initFilterBean() throws ServletException {
synchronized (this.delegateMonitor) {
if (this.delegate == null) {
// If no target bean name specified, use filter name.
if (this.targetBeanName == null) {
this.targetBeanName = getFilterName();
}
// Fetch Spring root application context and initialize the delegate early,
// if possible. If the root application context will be started after this
// filter proxy, we'll have to resort to lazy initialization.
WebApplicationContext wac = findWebApplicationContext();
if (wac != null) {
this.delegate = initDelegate(wac);
}
}
}
}
@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;
if (delegateToUse == null) {
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);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
Filter delegate = wac.getBean(getTargetBeanName(), Filter.class);
if (isTargetFilterLifecycle()) {
delegate.init(getFilterConfig());
}
return delegate;
}
// 省略一些代码
}
- 在initFilterBean方法初始化DelegatingFilterProxy对象或者第一次执行doFilter方法时会调用findWebApplicationContext方法查找上下文;
- targetBeanName表示被代理的过滤器bean的名称;
- delegate保存被代理的过滤器实例,该实例即是initDelegate方法从上下文中查找的名为targetBeanName的过滤器bean;
- delegateMonitor是初始化时用的锁。
doFilter方法利用invokeDelegate方法将调用委托给被代理的过滤器执行。
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
FilterChainProxy类
文档提到“Spring Security的基础设施只应该委托给一个FilterChainProxy实例,如果在web.xml中配置每个Spring Security的过滤器那就会显得很笨拙”。
FilterChainProxy继承自GenericFilterBean类,可以看成一个过滤器的集合。
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
".APPLIED");
private List<SecurityFilterChain> filterChains;
private FilterChainValidator filterChainValidator = new NullFilterChainValidator();
private HttpFirewall firewall = new DefaultHttpFirewall();
public FilterChainProxy() {
}
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
@Override
public void afterPropertiesSet() {
filterChainValidator.validate(this);
}
// 省略一些代码
}
- filterChains字段可以看成保存了所有与Spring Security相关的过滤器,这些过滤器由SecurityFilterChain组织起来。
public interface SecurityFilterChain { boolean matches(HttpServletRequest request); List<Filter> getFilters(); }
doFilter方法则调用了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);
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;
}
VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
vfc.doFilter(fwRequest, fwResponse);
}
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
- getFilters方法根据请求查找第一个匹配的SecurityFilterChain,因此SecurityFilterChain的添加顺序非常重要;
- VirtualFilterChain依次调用匹配的SecurityFilterChain中的每个过滤器。
Spring Security的过滤器
众所周知,Spring Security的实现基于servlet的过滤器,以下按照在SecurityFilterChain中的排列顺序列出了Spring Security中主要的过滤器:
- SecurityContextPersistenceFilter;
- CorsFilter;
- LogoutFilter;
- UsernamePasswordAuthenticationFilter、CasAuthenticationFilter和BasicAuthenticationFilter等认证过滤器;
- RememberMeAuthenticationFilter;
- AnonymousAuthenticationFilter;
- ExceptionTranslationFilter;
- FilterSecurityInterceptor。
更多过滤器可以参见Filter Ordering和Core Security Filters,下面简要分析各过滤器的功能。
1. SecurityContextPersistenceFilter
SecurityContextPersistenceFilter从所配置的SecurityContextRepository中获取与该请求关联的SecurityContext,并在请求结束后清除SecurityContextHolder。一般需要将该过滤器配置在其他认证过滤器前,因为认证过滤器如Basic、CAS等要求SecurityContextHolder包含有效的SecurityContext。
2. CorsFilter
CorsFilter根据CORS配置处理预检请求、简单请求和非简单请求,CORS配置由CorsConfiguration类表示。
3. LogoutFilter
若收到注销的请求,LogoutFilter会首先清除认证信息,然后依次执行配置的所有注销处理器LogoutHandler的logout方法,最后执行配置的注销成功处理器LogoutSuccessHandler的onLogoutSuccess方法。
4. UsernamePasswordAuthenticationFilter
若收到登录的请求,UsernamePasswordAuthenticationFilter执行基于表单提交的认证,默认配置下表单的用户名和密码字段分别是username和password,但是可以通过setUsernameParameter和setPasswordParameter进行配置,用户提交的用户名和密码则是通过obtainUsername和obtainPassword方法分别得到。具体的认证过程在该Filter的超类AbstractAuthenticationProcessingFilter中执行。
5. ExceptionTranslationFilter
ExceptionTranslationFilter处理位于它之后的过滤器如FilterSecurityInterceptor抛出的AccessDeniedException或AuthenticationException异常,它只是一个Java异常和HTTP响应之间的桥梁,并不做任何实际的安全认证。
- 处理AuthenticationException异常:该过滤器会委托给配置的AuthenticationEntryPoint处理;
- 处理AccessDeniedException异常:如果是匿名用户则委托给配置的AuthenticationEntryPoint处理,否则委托给配置的AccessDeniedHandler。
6. FilterSecurityInterceptor
FilterSecurityInterceptor用来保护HTTP资源,若没有认证则抛出AuthenticationException异常,若权限不足则抛出AccessDeniedException异常,这些异常都会被ExceptionTranslationFilter捕获。
参考文献
https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works
跨域资源共享 CORS 详解