Spring Security學習筆記-Filter
- SecurityContextPersistenceFilter
SecurityContextPersistenceFilter位於過濾器的頂端,是第一個起作用的過濾器。它的第一個用途是在執行其他過濾器之前率先判斷使用者的session是否已經存在了一個springSecurity上下文的securityContext,如果存在就把securityContext拿出來,放到securityContext的holder中,供springSecurity的其他部分使用;如果不存在,就建立一個securityContext出來,還是放到securityContext的holder中,供SpringSecurity的其他部分使用。它的第二個用途,是在所有過濾器執行完畢之後,清空securityContext的holder中的內容,因為securityContextHolder是基於threadLocal的,如果在操作完成後,沒有清空threadLocal,會受到伺服器的執行緒池機制的影響。
public class SecurityContextPersistenceFilter extends GenericFilterBean {
static final String FILTER_APPLIED = "__spring_security_scpf_applied";
private SecurityContextRepository repo;
private boolean forceEagerSessionCreation = false;
public SecurityContextPersistenceFilter() {
this (new HttpSessionSecurityContextRepository());
}
public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
this.repo = repo;
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
// Crucial removal of SecurityContextHolder contents - do this before anything
// else.
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
this.forceEagerSessionCreation = forceEagerSessionCreation;
}
}
- ThreadLocal(拓展)
ThreadLocal存放的值是執行緒內共享的,執行緒間互斥的,主要用於執行緒內共享一些資料,避免通過引數來傳遞,這樣處理後能夠優雅的解決一些實際中的併發問題。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先是set方法。我們可以看到,threadMap中有一個map,但這個map不是我們平時使用的map,而是ThreadLocalMap。ThreadLocalMap是ThreadLocal的一個內部類,是不對外使用的,當使用ThreadLocal存值時,首先獲取到當前執行緒物件,然後獲取到當前執行緒本地的物件,本地變數map,最後將當前使用的ThreadLocal和傳入的值放在map中,也就是說,ThreadLocalMap中儲存值的key是ThreadLocal物件。這樣做的好處是每個執行緒都對應一個本地變數的map,所以一個執行緒可以存在多個執行緒本地變數。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
接下來是get方法。如果沒有執行過set操作,那麼從ThreadLocal中拿到的值就是空,這是get方法會返回初始值,也就是呼叫這裡面的初始值的方法(setInitialValue())。ThreadLocal中,這個方法預設返回空,當我們有需要第一次get就能得到一個值時,是可以繼承ThreadLocal並且覆蓋初始值這個方法的。ThreadLocal是解決執行緒安全問題的一個很好的思路,它通過為每一個執行緒提供一個獨立的變數副本,解決變數併發訪問的衝突問題,在很多情況,ThreadLocal它的同步機制解決執行緒安全問題會變得非常簡單,非常方便,而且程式擁有更高的併發性。當我們使用ThreadLocal的時候,一定要注意當一個執行緒結束時,一定要把當前ThreadLocal中的資訊移除掉。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
系統為我們提供了一個remove方法,我們只要呼叫這個方法就可以了,這個時候它就會清掉當前執行緒攜帶的資訊。
3. LogoutFilter
LogoutFilter只處理登出請求,預設處理的請求為j_spring_security_logout。它的用途是在使用者傳送登出請求時,銷燬使用者的session,清空SecurityContextHolder,然後重定向到登出成功頁面,可以與RememberMe功能結合,在登出的同時,清空使用者的cookie。
4. AbstractAuthenticationProcessingFilter
AbstractAuthenticationProcessingFilter是處理form登陸的過濾器,與form有關的所有操作都在此進行。預設情況下,它處理的是j_spring_security_check這個請求。這個請求是使用者使用form登陸的提交地址,此過濾器執行基本操作時,是通過使用者名稱和密碼判斷使用者是否有效,如果登陸成功,就跳轉到成功頁面,它可能是登陸之前訪問的受保護頁面,也可能是預設的成功頁面;如果登陸失敗,則跳轉到失敗頁面。
5. DefaultLoginPageGeneratingFilter
DefaultLoginPageGeneratingFilter用來生成一個預設的登陸頁面,預設的訪問地址為spring _security_login,這個預設登陸頁面雖然支援使用者輸入使用者名稱密碼,也支援RememberMe的功能,但是因為很簡陋,只能在演示時做個樣子,不能在實際專案中使用。
6. BasicAuthenticationFilter
BasicAuthenticationFilter主要用來進行basic驗證,功能與前面的AbstractAuthenticationProcessingFilter很類似,只是驗證的方式不同。
7. SecurityContextHolderAwareRequestFilter
SecurityContextHolderAwareRequestFilter用來包裝客戶端的請求,目的是在原來請求的基礎之上為後續程式提供一些額外的資料。
8. RememberMeAuthenticationFilter
RememberMeAuthenticationFilter實現的是RememberMe功能,當用戶庫位中存在RememberMe標記時,它會根據標記,自動實現使用者登陸並建立SecurityContext所對應的許可權,SpringSecurity中的RememberMe是依賴cookie實現的,當用戶在登陸的時候選擇使用RememberMe時,系統就會在登陸成功後,為使用者生成一個唯一的標識,並將這個標識儲存進cookie中,我們可以通過瀏覽器檢視使用者電腦中的cookie。
9. AnonymousAuthenticationFilter
AnonymousAuthenticationFilter是為了保障操作統一性,當用戶沒有登陸時,預設為使用者分配匿名使用者的許可權(許多專案會關閉匿名使用者)。
10. ExceptionTranslationFilter
ExceptionTranslationFilter是為了處理FilterSecurityIntercepter中丟擲的異常,然後將請求重定向到對應頁面或返回對應的錯誤程式碼。
11. SessionManagementFilter
SessionManagementFilter是為了防禦會話偽造攻擊,主要是在使用者成功登陸之後,銷燬使用者當前的session並重新生成一個session。
12. FilterSecurityInterceptor
FilterSecurityInterceptor包含使用者所有的許可權控制。它的第一個功能是,如果使用者尚未登陸則丟擲尚未認證的異常,它的第二個功能是如果使用者已登入,但是沒有訪問當前資源的許可權則會丟擲拒絕訪問的異常,它的第三個功能是如果使用者已登入並且具有訪問當前內容的許可權,則放行。
13. FilterChainProxy
FilterChainProxy會按照順序來呼叫一組filter,使這些filter既能完成驗證授權的本職工作,又能享用springIOC的功能並很方便的得到其他依賴的資源。