寫在前面
上回我們講了spring security整合spring springmvc的流程,並且知道了spring security是通過過濾器鏈來進行認證授權操作的。今天我們來分析一下springsecurity過濾器鏈的加載流程。讀者在閱讀本文時可以邊閱讀邊跟着操作,這樣子會理解的更清楚一些。
Spring Security過濾器鏈
spring security的過濾器非常多,這里簡單介紹幾個常用的過濾器。
spring security常過濾器鏈介紹
org.springframework.security.web.context.SecurityContextPersistenceFilter
主要是使用SecurityContextRepository在session中保存或更新一個SecurityContext域對象(相當於一個容器),並將SecurityContext給以后的過濾器使用,來為后續filter建立所需的上下文。SecurityContext中存儲了當前用戶的認證以及權限信息。 其他的過濾器都需要依賴於它。在 Spring Security 中,雖然安全上下文信息被存儲於 Session 中,但我們在實際使用中不應該直接操作 Session,而應當使用 SecurityContextHolder。
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter
這個過濾器用於集成SecurityContext到Spring異步執行機制的WebAsyncManager 中。如果想要與spring集成,就必須要使用此過濾器鏈。
org.springframework.security.web.csrf.CsrfFilter
csrf又稱跨域請求偽造,SpringSecurity會對所有post請求驗證是否包含系統生成的csrf的token信息,如果不包含,則報錯。起到防止csrf攻擊的效果。
題外話:csrf攻擊
CSRF(Cross-site request forgery)跨站請求偽造:攻擊者誘導受害者進入第三方網站,在第三方網站中,向被攻擊網站發送跨站請求。利用受害者在被攻擊網站已經獲取的注冊憑證,繞過后台的用戶驗證,達到冒充用戶對被攻擊的網站執行某項操作的目的。
一個例子:
比如你登陸了銀行網站A之后,瀏覽器cookie中將保存你的登錄信息。同時你沒有沒有注銷登錄的情況下又用同一瀏覽器訪問了視頻網站B,假設視頻網站B中含有csrf病毒,其將獲取你的cookie內容,拿到你銀行網站A的登錄信息,就可以進行不可描述之操作了。
而這個CsrfFilter通過簽發token的方式進行訪問驗證,如果token不是本網站簽發的或者訪問請求中不帶有這個token則拒絕訪問。
org.springframework.security.web.authentication.logout.LogoutFilter
匹配URL為/logout的請求,實現用戶退出,清除認證信息。
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
認證操作全靠這個過濾器,默認匹配URL為/login且必須為POST請求。
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter
如果沒有在配置文件中指定認證頁面,則由該過濾器生成一個默認認證頁面
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter
由此過濾器可以生產一個默認的退出登錄頁面
org.springframework.security.web.authentication.www.BasicAuthenticationFilter
此過濾器會自動解析HTTP請求中頭部名字為Authentication,且以Basic開頭的頭信息。
org.springframework.security.web.savedrequest.RequestCacheAwareFilter
通過HttpSessionRequestCache內部維護了一個RequestCache,用於緩存HttpServletRequest
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter
針對ServletRequest進行了一次包裝,使得request具有更加豐富的API
org.springframework.security.web.authentication.AnonymousAuthenticationFilter
當SecurityContextHolder中認證信息為空,則會創建一個匿名用戶存入到SecurityContextHolder中。
spring security為了兼容未登錄的訪問,也走了一套認證流程,只不過是一個匿名的身份(游客)。
org.springframework.security.web.session.SessionManagementFilter
SecurityContextRepository限制同一用戶開啟多個會話的數量
org.springframework.security.web.access.ExceptionTranslationFilter
異常轉換過濾器位於整個springSecurityFilterChain的后方,用來轉換整個鏈路中出現的異常
org.springframework.security.web.access.intercept.FilterSecurityInterceptor
獲取所配置資源訪問的授權信息,根據SecurityContextHolder中存儲的用戶信息來決定其是否有權限。
spring security加載流程
在web.xml中,我們配置了一個名字為“springSecurityFilterChain”的過濾器org.springframework.web.filter.DelegatingFilterProxy。以下是部分源碼。
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
private boolean targetFilterLifecycle;
/** 真正加載的過濾器*/
@Nullable
private volatile Filter delegate;
private final Object delegateMonitor;
protected void initFilterBean() throws ServletException {
synchronized(this.delegateMonitor) {
if (this.delegate == null) {
if (this.targetBeanName == null) {
this.targetBeanName = this.getFilterName();
}
WebApplicationContext wac = this.findWebApplicationContext();
if (wac != null) {
this.delegate = this.initDelegate(wac);
}
}
}
}
/** 過濾器的入口 */
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Filter delegateToUse = this.delegate;
if (delegateToUse == null) {
synchronized(this.delegateMonitor) {
delegateToUse = this.delegate;
if (delegateToUse == null) {
WebApplicationContext wac = this.findWebApplicationContext();
if (wac == null) {
throw new IllegalStateException("No WebApplicationContext found: no ContextLoaderListener or DispatcherServlet registered?");
}
/** 在做完一系列判斷之后,真正要做的就是這一步,初始化delegate*/
delegateToUse = this.initDelegate(wac);
}
this.delegate = delegateToUse;
}
}
this.invokeDelegate(delegateToUse, request, response, filterChain);
}
protected Filter initDelegate(WebApplicationContext wac) throws ServletException {
String targetBeanName = this.getTargetBeanName();
Assert.state(targetBeanName != null, "No target bean name set");
Filter delegate = (Filter)wac.getBean(targetBeanName, Filter.class);
if (this.isTargetFilterLifecycle()) {
delegate.init(this.getFilterConfig());
}
return delegate;
}
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException {
delegate.doFilter(request, response, filterChain);
}
}
接下來我們在initDelegate方法上打個斷點調試一下
最終我們發現,通過initDelegate方法給delegate初始化,得到一個FilterChainProxy對象。接下來我們進入FilterChainProxy這個類看看
public class FilterChainProxy extends GenericFilterBean {
。。。。
}
可以發現這也是一個過濾器,那么我們找dofilter方法。
接下來我們在doFilterInternal里面打個斷點試試,看看能不能得到過濾器鏈
我們在這一步得到了一個List
我們再進入到getFilters中看看這些過濾器鏈在哪
private List<Filter> getFilters(HttpServletRequest request) {
Iterator var2 = this.filterChains.iterator();
SecurityFilterChain chain;
do {
if (!var2.hasNext()) {
return null;
}
chain = (SecurityFilterChain)var2.next();
} while(!chain.matches(request));
return chain.getFilters();
}
從該方法我們知道過濾器鏈來自於SecurityFilterChain的getFilters方法,接下來,我們看看這個類
public interface SecurityFilterChain {
boolean matches(HttpServletRequest var1);
List<Filter> getFilters();
}
可以發現這是一個接口,在idea中ctrl+H可以發現它只有一個實現類,那就是DefaultSecurityFilterChain
到這一步,我們可以發現,springSecurity的過濾器鏈存在於SecurityFilterChain中(springSecurity的過濾器鏈由SecurityFilterChain負責封裝),它只有一個實現類那就是DefaultSecurityFilterChain。也就是說其過濾器鏈由SecurityFilterChain封裝。
至此,springSecurity的過濾器鏈加載流程我們就說完了。