魯春利的工做筆記,好記性不如爛筆頭java
ShiroFilterweb
ShiroFilter,它是在整個 Shiro Web 應用中請求的門戶,全部的請求都會被 ShiroFilter 攔截並進行相應的鏈式處理。ShiroFilter 往上有五層,最上層是 Filter(即 javax.servlet.Filter),它是 Servlet 規範中的 Filter 接口。
apache
一、AbstractFilter安全
Shiro經過抽象類對Servlet的Filter接口進行了封裝,並經過繼承ServletContextSupport對ServletContext也進行了封裝。app
package org.apache.shiro.web.servlet; // ServletContextSupport對ServletContext進行了封裝 public abstract class AbstractFilter extends ServletContextSupport implements Filter { protected FilterConfig filterConfig; public FilterConfig getFilterConfig() { return filterConfig; } // 初始化 FilterConfig 與 ServletContext public void setFilterConfig(FilterConfig filterConfig) { this.filterConfig = filterConfig; // 調用ServletContextSupport的方法來封裝ServletContext setServletContext(filterConfig.getServletContext()); } // 從 FilterConfig 中獲取初始參數 protected String getInitParam(String paramName) { FilterConfig config = getFilterConfig(); if (config != null) { return StringUtils.clean(config.getInitParameter(paramName)); } return null; } public final void init(FilterConfig filterConfig) throws ServletException { // 初始化 FilterConfig setFilterConfig(filterConfig); try { // 在子類中實現該模板方法 onFilterConfigSet(); } catch (Exception e) { // 異常信息 } } }
Shiro對ServletContext的封裝異步
Shiro 爲了封裝 ServletContext 的而提供的一個類ServletContextSupportide
package org.apache.shiro.web.servlet; // public class ServletContextSupport { private ServletContext servletContext = null; public ServletContext getServletContext() { return servletContext; } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } // 其餘代碼略 }
二、NameableFilterui
提供了Filter Name的獲取方式。this
package org.apache.shiro.web.servlet; public abstract class NameableFilter extends AbstractFilter implements Nameable { /** * The name of this filter, unique within an application. */ private String name; // 若成員變量 name 爲空,則從 FilterConfig 中獲取 Filter Name protected String getName() { if (this.name == null) { FilterConfig config = getFilterConfig(); if (config != null) { this.name = config.getFilterName(); } } return this.name; } public void setName(String name) { this.name = name; } }
每一個 Filter 必須有一個名字,可經過 setName 方法設置的,若是不設置就取該 Filter 默認的名字,也就是在 web.xml 中配置的 filter-name 了。
url
三、OncePerRequestFilter
NameableFilter是爲了讓每一個 Filter 有一個名字,並且這個名字必須是惟一的。此外,在 shiro.ini 的 [urls] 片斷的配置要求知足必定規則,例如:
[urls] /foo = ssl, authc
等號左邊的是 URL,右邊的是 Filter Chian,一個或多個 Filter,每一個 Filter 用逗號進行分隔。
對於 /foo 這個 URL 而言,可前後經過 ssl 與 authc 這兩個 Filter。若是咱們同時配置了兩個 ssl,這個 URL 會被 ssl 攔截兩次嗎?答案是否認的,由於 Shiro 爲咱們提供了一個「一次性Filter」的原則,也就是保證了每一個請求只能被同一個 Filter 攔截一次,並且僅此一次。
package org.apache.shiro.web.servlet; public abstract class OncePerRequestFilter extends NameableFilter { /** * 已執行過的過濾器("already filtered")附加的後綴名 * * @see #getAlreadyFilteredAttributeName */ public static final String ALREADY_FILTERED_SUFFIX = ".FILTERED"; /** * 是否開啓過濾功能 * * @see #isEnabled() */ private boolean enabled = true; //most filters wish to execute when configured, so default to true public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 獲取 Filter 已過濾的屬性名 String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); // 判斷是否已過濾 if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { // 若已過濾,則進入 FilterChain 中下一個 Filter filterChain.doFilter(request, response); // 若未過濾,則判斷是否未開啓過濾功能(其中 shouldNotFilter 方法將被廢棄 } else if (!isEnabled(request, response) || shouldNotFilter(request) ) { // 若未開啓,則進入 FilterChain 中下一個 Filter filterChain.doFilter(request, response); } else { // Do invoke this filter...(執行過濾) // 將已過濾屬性設置爲 true(只要保證 Request 中有這個屬性便可) request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { // 在子類中執行具體的過濾操做 doFilterInternal(request, response, filterChain); } finally { // 當前 Filter 執行結束需移除 Request 中的已過濾屬性 request.removeAttribute(alreadyFilteredAttributeName); } } } protected String getAlreadyFilteredAttributeName() { String name = getName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; } // 抽象方法,由子類實現 protected abstract void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException; }
如何確保每一個請求只會被同一個 Filter 攔截一次呢?Shiro 提供了一個超簡單的解決方案:在 Requet 中放置一個後綴爲 .FILTERED 的屬性,在執行具體攔截操做(即 doFilterInternal 方法)以前放入該屬性,執行完畢後移除該屬性。
在 Shiro 的 Filter Chian 配置中,若是咱們想禁用某個 Filter,如何實現呢?OncePerRequestFilter 也爲咱們提供了一個 enabled 的屬性,方便咱們能夠在 shiro.ini 中隨時禁用某個 Filter,例如:
[main] ssl.enabled = false [urls] /foo = ssl, authc
這樣一來 ssl 這個 Filter 就被咱們給禁用了,之後想開啓 ssl 的話,徹底不須要在 urls配置中一個個手工來添加,只需把 ssl.enabled 設置爲 true,或註釋掉該行,或直接刪除該行便可。
可見,OncePerRequestFilter 給咱們提供了一個模板方法doFilterInternal,在其子類中咱們須要實現該方法的具體細節,那麼誰來實現呢?
3.一、AbstractShiroFilter
package org.apache.shiro.web.servlet; /** * 確保可經過 SecurityUtils 獲取 SecurityManager,並執行過濾器操做 */ public abstract class AbstractShiroFilter extends OncePerRequestFilter { // 是否能夠經過 SecurityUtils 獲取 SecurityManager private static final String STATIC_INIT_PARAM_NAME = "staticSecurityManagerEnabled"; // Reference to the security manager used by this filter private WebSecurityManager securityManager; // Used to determine which chain should handle an incoming request/response private FilterChainResolver filterChainResolver; private boolean staticSecurityManagerEnabled; protected AbstractShiroFilter() { this.staticSecurityManagerEnabled = false; } public WebSecurityManager getSecurityManager() { return securityManager; } public void setSecurityManager(WebSecurityManager sm) { this.securityManager = sm; } public FilterChainResolver getFilterChainResolver() { return filterChainResolver; } public void setFilterChainResolver(FilterChainResolver filterChainResolver) { this.filterChainResolver = filterChainResolver; } // 在AbstractFilter.ini()中調用子類的實現 protected final void onFilterConfigSet() throws Exception { //added in 1.2 for SHIRO-287: // 從 web.xml 中讀取 staticSecurityManagerEnabled 參數(默認爲 false) applyStaticSecurityManagerEnabledConfig(); // 初始化(在子類中實現) init(); // 確保 SecurityManager 必須存在 ensureSecurityManager(); //added in 1.2 for SHIRO-287: // 若已開啓 static 標誌,則將當前的 SecurityManager 放入 SecurityUtils 中,之後能夠隨時獲取 if (isStaticSecurityManagerEnabled()) { SecurityUtils.setSecurityManager(getSecurityManager()); } } public void init() throws Exception { // 空方法體 } // OncePerRequestFilter.doFilter 時須要執行的方法 protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)throws ServletException, IOException { Throwable t = null; try { // 經過ShiroHttpServletRequest對ServletRequest進行了封裝 final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); // 經過ShiroHttpServletResponse對ServletResponse進行了封裝 final ServletResponse response = prepareServletResponse(request, servletResponse, chain); // 建立 Shiro 的 Subject 對象(WebSubject) final Subject subject = createSubject(request, response); // 使用異步的方式執行相關操做 //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { // 更新 Session 的最後訪問時間 updateSessionLastAccessTime(request, response); // 執行 Shiro 的 Filter Chain executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { // 異常處理 } catch (Throwable throwable) { // 異常處理 } if (t != null) { // 異常處理 } } protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { // 獲取 Shiro 代理後的 FilterChain 對象,並進行鏈式處理 FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); } protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; // 在ShiroFilter的init方法中設置FilterChainResolver FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } // 經過 FilterChainResolver(PathMatchingFilterChainResolver) 獲取 ProxiedFilterChain FilterChain resolved = resolver.getChain(request, response, origChain); if (resolved != null) { log.trace("Resolved a configured FilterChain for the current request."); chain = resolved; } else { log.trace("No FilterChain configured for the current request. Using the default."); } return chain; } }
3.1.一、ShiroFilter
package org.apache.shiro.web.servlet; public class ShiroFilter extends AbstractShiroFilter { @Override public void init() throws Exception { // 從 ServletContext 中獲取 WebEnvironment(該對象已經過 EnvironmentLoader 建立) // 實際實現爲:經過ServletContext獲取到web.xml文件中定義的shiroEnvironmentClass WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext()); // IniWebEnvironment.init會生成SecurityManager,在getWebSecurityManager時直接獲取到 setSecurityManager(env.getWebSecurityManager()); // 經過IniFilterChainResolverFactory.createDefaultInstance獲取PathMatchingFilterChainResolver FilterChainResolver resolver = env.getFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } } }
在 ShiroFilter 中只用作初始化的行爲,就是從 WebEnvironment 中分別獲取 WebSecurityManager與FilterChainResolver,其它的事情都由它的父類去實現了。實際上ShiroFilter 還實現了一些其餘的封裝,例如: 經過 ShiroHttpServletRequest 來包裝 Request 經過 ShiroHttpServletResponse 來包裝 Response 經過 Session 來代理 HttpSession 提供 FilterChain 的代理機制 使用 ThreadContext 來保證線程安全