Spring Security文檔的The Security Filter Chain一章指出Spring Security完全基于標(biāo)準(zhǔn)的servlet過濾器悬包,本文結(jié)合筆者的看法對該文檔進(jìn)行一些補(bǔ)充說明魄梯。
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對象或者第一次執(zhí)行doFilter方法時(shí)會(huì)調(diào)用findWebApplicationContext方法查找上下文;
- targetBeanName表示被代理的過濾器bean的名稱番宁;
- delegate保存被代理的過濾器實(shí)例元莫,該實(shí)例即是initDelegate方法從上下文中查找的名為targetBeanName的過濾器bean;
- delegateMonitor是初始化時(shí)用的鎖蝶押。
doFilter方法利用invokeDelegate方法將調(diào)用委托給被代理的過濾器執(zhí)行踱蠢。
protected void invokeDelegate(
Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
FilterChainProxy類
文檔提到“Spring Security的基礎(chǔ)設(shè)施只應(yīng)該委托給一個(gè)FilterChainProxy實(shí)例,如果在web.xml中配置每個(gè)Spring Security的過濾器那就會(huì)顯得很笨拙”棋电。
FilterChainProxy繼承自GenericFilterBean類茎截,可以看成一個(gè)過濾器的集合。
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相關(guān)的過濾器赶盔,這些過濾器由SecurityFilterChain組織起來企锌。
public interface SecurityFilterChain { boolean matches(HttpServletRequest request); List<Filter> getFilters(); }
doFilter方法則調(diào)用了doFilterInternal方法,該方法及相關(guān)代碼如下:
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方法根據(jù)請求查找第一個(gè)匹配的SecurityFilterChain于未,因此SecurityFilterChain的添加順序非常重要撕攒;
- VirtualFilterChain依次調(diào)用匹配的SecurityFilterChain中的每個(gè)過濾器。
Spring Security的過濾器
眾所周知烘浦,Spring Security的實(shí)現(xiàn)基于servlet的過濾器打却,以下按照在SecurityFilterChain中的排列順序列出了Spring Security中主要的過濾器:
- SecurityContextPersistenceFilter;
- CorsFilter谎倔;
- LogoutFilter;
- UsernamePasswordAuthenticationFilter猿推、CasAuthenticationFilter和BasicAuthenticationFilter等認(rèn)證過濾器片习;
- RememberMeAuthenticationFilter;
- AnonymousAuthenticationFilter蹬叭;
- ExceptionTranslationFilter藕咏;
- FilterSecurityInterceptor。
更多過濾器可以參見Filter Ordering和Core Security Filters秽五,下面簡要分析各過濾器的功能孽查。
1. SecurityContextPersistenceFilter
SecurityContextPersistenceFilter從所配置的SecurityContextRepository中獲取與該請求關(guān)聯(lián)的SecurityContext,并在請求結(jié)束后清除SecurityContextHolder坦喘。一般需要將該過濾器配置在其他認(rèn)證過濾器前盲再,因?yàn)檎J(rèn)證過濾器如Basic、CAS等要求SecurityContextHolder包含有效的SecurityContext瓣铣。
2. CorsFilter
CorsFilter根據(jù)CORS配置處理預(yù)檢請求答朋、簡單請求和非簡單請求,CORS配置由CorsConfiguration類表示棠笑。
3. LogoutFilter
若收到注銷的請求梦碗,LogoutFilter會(huì)首先清除認(rèn)證信息,然后依次執(zhí)行配置的所有注銷處理器LogoutHandler的logout方法,最后執(zhí)行配置的注銷成功處理器LogoutSuccessHandler的onLogoutSuccess方法洪规。
4. UsernamePasswordAuthenticationFilter
若收到登錄的請求印屁,UsernamePasswordAuthenticationFilter執(zhí)行基于表單提交的認(rèn)證,默認(rèn)配置下表單的用戶名和密碼字段分別是username和password斩例,但是可以通過setUsernameParameter和setPasswordParameter進(jìn)行配置雄人,用戶提交的用戶名和密碼則是通過obtainUsername和obtainPassword方法分別得到。具體的認(rèn)證過程在該Filter的超類AbstractAuthenticationProcessingFilter中執(zhí)行樱拴。
5. ExceptionTranslationFilter
ExceptionTranslationFilter處理位于它之后的過濾器如FilterSecurityInterceptor拋出的AccessDeniedException或AuthenticationException異常柠衍,它只是一個(gè)Java異常和HTTP響應(yīng)之間的橋梁,并不做任何實(shí)際的安全認(rèn)證晶乔。
- 處理AuthenticationException異常:該過濾器會(huì)委托給配置的AuthenticationEntryPoint處理珍坊;
- 處理AccessDeniedException異常:如果是匿名用戶則委托給配置的AuthenticationEntryPoint處理,否則委托給配置的AccessDeniedHandler正罢。
6. FilterSecurityInterceptor
FilterSecurityInterceptor用來保護(hù)HTTP資源阵漏,若沒有認(rèn)證則拋出AuthenticationException異常,若權(quán)限不足則拋出AccessDeniedException異常翻具,這些異常都會(huì)被ExceptionTranslationFilter捕獲履怯。
參考文獻(xiàn)
https://stackoverflow.com/questions/41480102/how-spring-security-filter-chain-works
跨域資源共享 CORS 詳解