shiro是咱們在項目常常使用到的權限管理框架,本文咱們就重點來分析FormAuthenticationFilter的驗證過程。java
咱們首先來看下FormAuthenticationFilter的繼承結構,這樣更加便於咱們去分析app
從上面的繼承結構圖裏咱們發現FormAuthenticationFilter的繼承結構仍是蠻負責的,咱們先一個個來介紹下他們的做用。而後具體分析框架
類 | 做用 |
---|---|
AbstractFilter | 加載FilterConfig的相關信息 |
NameableFilter | 定義每個filter的名字 |
OncePerRequestFilter | 保證客戶端請求後該filter的doFilter只會執行一次 |
AdviceFilter | 主要是對doFilterInternal作了更細緻的處理 |
PathMatchingFilter | 主要是對preHandle作進一步細化控制 |
AccessControlFilter | 對onPreHandle方法作了進一步細化 |
AuthenticationFilter AuthenticatingFilter |
用來作認證的Filter |
FormAuthenticationFiltershiro | 用來具體的實現登陸的Filter |
接下具體看下每一個Filter中主要的方法的做用及實現。ide
NameableFilter有一個name屬性,定義每個filter的名字.在FilterChainManager 中會調用配置文件中的配置屬性名字來爲每個filter命名以及爲默認的filter命名,如authc.post
OncePerRequestFilter保證客戶端請求後該filter的doFilter只會執行一次.當知足攔截條件的請求到來的時候執行的就是本方法中的OncePerRequestFilter 中的doFilter方法:this
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。debug
enabled狀態 | 說明 |
---|---|
true | 表示是否啓用這個filter,默認是true |
false | 跳過這個filter的doFilterInternal方法而去執行filter鏈中其餘filter |
在OncePerRequestFilter中調用的doFilterInternal方法是個抽象方法,具體的實現是在AdviceFilter中,並且AdviceFilter對於doFilterInternal方法作了更細緻的處理。以下:3d
此處的處理和SpringMVC中的interceptor很相似。code
方法 | 說明 |
---|---|
preHandle | 前置的判斷處理,若是返回false則filter鏈不繼續往下執行 |
postHandle | 在目標方法(即客戶端請求的接口)正常(未拋出異常)執行後完成一些操做,默認不作任何操做 |
cleanup | 會調用afterCompletion方法,無論目標方法是否出現異常都會繼續操做。默認也是空 |
AdviceFilter整體是對OncePerRequestFilter中的doFilterInternal進一步細化控制orm
PathMatchingFilter主要是對preHandle作進一步細化控制,該filter爲抽象類,其餘路徑直接經過:preHandle中,若請求的路徑非該filter中配置的攔截路徑,則直接返回true進行下一個filter。若包含在此filter路徑中,則會在isFilterChainContinued作一些控制,該方法中會調用onPreHandle方法,因此子類能夠在onPreHandle中編寫filter控制流程代碼(返回true或false)
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中看到)
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方法,在登陸成功或者失敗時作一些操做。登陸將在下面詳細說明
FormAuthenticationFiltershiro提供的登陸的filter,若是用戶未登陸,即AuthenticatingFilter中的isAccessAllowed判斷了用戶未登陸,則會調用onAccessDenied方法作用戶登陸操做。若用戶請求的不是登陸地址,則跳轉到登陸地址,而且返回false直接終止filter鏈。若用戶請求的是登陸地址,若果是post請求則進行登陸操做,由AuthenticatingFilter中提供的executeLogin方法執行。不然直接經過繼續執行filter鏈,並最終跳轉到登陸頁面(由於用戶請求的就是登陸地址,若不是登陸地址也會重定向到登陸地址)
在來看executeLogin方法,
onLoginSuccess
onLoginFailure
若登陸成功返回false(FormAuthenticationFiltershiro的onLoginSuccess默認false),則表示終止filter鏈,直接重定向到成功頁面,甚至不到達目標方法直接返回了。若登陸失敗,直接返回true(onLoginFailure返回false),繼續執行filter鏈並最終跳轉到登陸頁面,該方法還會設置一些登陸失敗提示 ==shiroLoginFailure==,在目標方法中能夠根據這個錯誤提示制定客戶端更加友好的錯誤提示。