shiro是我們在項目經常使用到的權限管理框架,本文我們就重點來分析FormAuthenticationFilter的驗證過程。
FormAuthenticationFilter
1.繼承結構
我們首先來看下FormAuthenticationFilter的繼承結構,這樣更加便於我們去分析
2.父類的作用
從上面的繼承結構圖里我們發現FormAuthenticationFilter的繼承結構還是蠻負責的,我們先一個個來介紹下他們的作用。然后具體分析
類 | 作用 |
---|---|
AbstractFilter | 加載FilterConfig的相關信息 |
NameableFilter | 定義每一個filter的名字 |
OncePerRequestFilter | 保證客戶端請求后該filter的doFilter只會執行一次 |
AdviceFilter | 主要是對doFilterInternal做了更細致的處理 |
PathMatchingFilter | 主要是對preHandle做進一步細化控制 |
AccessControlFilter | 對onPreHandle方法做了進一步細化 |
AuthenticationFilter AuthenticatingFilter |
用來做認證的Filter |
FormAuthenticationFiltershiro | 用來具體的實現登錄的Filter |
3.具體分析
接下具體看下每個Filter中主要的方法的作用及實現。
3.1 NameableFilter
NameableFilter有一個name屬性,定義每一個filter的名字.在FilterChainManager 中會調用配置文件中的配置屬性名字來為每一個filter命名以及為默認的filter命名,如authc.
3.2 OncePerRequestFilter
OncePerRequestFilter保證客戶端請求后該filter的doFilter只會執行一次.當滿足攔截條件的請求到來的時候執行的就是本方法中的OncePerRequestFilter 中的doFilter方法:
public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());
filterChain.doFilter(request, response);
} else //noinspection deprecation
if (/* added in 1.2: */ !isEnabled(request, response) ||
/* retain backwards compatibility: */ shouldNotFilter(request) ) {
log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.",
getName());
filterChain.doFilter(request, response);
} else {
// Do invoke this filter...
log.trace("Filter '{}' not yet executed. Executing now.", getName());
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
// 核心代碼,保證這個方法只會執行一次
doFilterInternal(request, response, filterChain);
} finally {
// Once the request has finished, we're done and we don't
// need to mark as 'already filtered' any more.
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}
doFilter的實質內容是在doFilterInternal方法中完成的。所以實質上是保證每一個filter的 doFilterInternal只會被執行一次,例如在配置中配置路徑 /user/** = authc,authc.則只會執行authc中的doFilterInternal一次。doFilterInternal非常重要,在shiro整個filter體系中的核心方法及實質入口。另外,shiro是通過在request中設置一個該filter特定的屬性值來保證該filter只會執行一次的。
enabled屬性決定了是否執行本Filter。
enabled狀態 | 說明 |
---|---|
true | 表示是否啟用這個filter,默認是true |
false | 跳過這個filter的doFilterInternal方法而去執行filter鏈中其他filter |
3.3 AdviceFilter
在OncePerRequestFilter中調用的doFilterInternal方法是個抽象方法,具體的實現是在AdviceFilter中,而且AdviceFilter對於doFilterInternal方法做了更細致的處理。如下:
此處的處理和SpringMVC中的interceptor很類似。
方法 | 說明 |
---|---|
preHandle | 前置的判斷處理,如果返回false則filter鏈不繼續往下執行 |
postHandle | 在目標方法(即客戶端請求的接口)正常(未拋出異常)執行后完成一些操作,默認不做任何操作 |
cleanup | 會調用afterCompletion方法,不管目標方法是否出現異常都會繼續操作。默認也是空 |
AdviceFilter總體是對OncePerRequestFilter中的doFilterInternal進一步細化控制
3.4 PathMatchingFilter
PathMatchingFilter主要是對preHandle做進一步細化控制,該filter為抽象類,其他路徑直接通過:preHandle中,若請求的路徑非該filter中配置的攔截路徑,則直接返回true進行下一個filter。若包含在此filter路徑中,則會在isFilterChainContinued做一些控制,該方法中會調用onPreHandle方法,所以子類可以在onPreHandle中編寫filter控制流程代碼(返回true或false)
3.5 AccessControlFilter
onPreHandle方法在PathMatchingFilter就簡單的返回了true,在AccessControlFilter 中更細化的實現了。
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue)
throws Exception {
return isAccessAllowed(request, response, mappedValue)
|| onAccessDenied(request, response, mappedValue);
}
isAccessAllowed方法和onAccessDenied方法達到控制效果。這兩個方法都是抽象方法,由子類去實現。到這一層應該明白。isAccessAllowed和onAccessDenied方法會影響到onPreHandle方法,而onPreHandle方法會影響到preHandle方法,而preHandle方法會達到控制filter鏈是否執行下去的效果。所以如果正在執行的filter中isAccessAllowed和onAccessDenied都返回false,則整個filter控制鏈都將結束,不會到達目標方法(客戶端請求的接口),而是直接跳轉到某個頁面(由filter定義的,將會在authc中看到)
3.6 AuthenticatingFilter
AuthenticationFilter和AuthenticatingFilter認證的filter,在抽象類中AuthenticatingFilter實現了isAccessAllowed方法,該方法是用來判斷用戶是否已登錄,若未登錄再判斷是否請求的是登錄地址,是登錄地址則放行,否則返回false終止filter鏈。
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue)
||(!isLoginRequest(request, response)
&& isPermissive(mappedValue));
}
提供了executeLogin方法實現用戶登錄的,還定義了onLoginSuccess和onLoginFailure方法,在登錄成功或者失敗時做一些操作。登錄將在下面詳細說明
3.7 FormAuthenticationFiltershiro
FormAuthenticationFiltershiro提供的登錄的filter,如果用戶未登錄,即AuthenticatingFilter中的isAccessAllowed判斷了用戶未登錄,則會調用onAccessDenied方法做用戶登錄操作。若用戶請求的不是登錄地址,則跳轉到登錄地址,並且返回false直接終止filter鏈。若用戶請求的是登錄地址,若果是post請求則進行登錄操作,由AuthenticatingFilter中提供的executeLogin方法執行。否則直接通過繼續執行filter鏈,並最終跳轉到登錄頁面(因為用戶請求的就是登錄地址,若不是登錄地址也會重定向到登錄地址)
在來看executeLogin方法,
onLoginSuccess
onLoginFailure
若登錄成功返回false(FormAuthenticationFiltershiro的onLoginSuccess默認false),則表示終止filter鏈,直接重定向到成功頁面,甚至不到達目標方法直接返回了。若登錄失敗,直接返回true(onLoginFailure返回false),繼續執行filter鏈並最終跳轉到登錄頁面,該方法還會設置一些登錄失敗提示 shiroLoginFailure,在目標方法中可以根據這個錯誤提示制定客戶端更加友好的錯誤提示。