shiro學習筆記:整合spring之攔截器鏈執行流程
阿新 • • 發佈:2019-01-27
一、環境準備
搭建好spring + shiro整合環境(本文環境Spring 4.3.10.RELEASE + Shiro 1.4.0)後,編寫登入頁面如下:shiro攔截器部分配置:<html> <head> <title>登入頁</title> </head> <body> <div style="color:red;">${shiroLoginFailure}</div> <form action="" method="post"> 使用者名稱:<input type="text" name="username"><br /> 密碼:<input type="password" name="password"><br /> <input type="submit" value="登入"> </form> </body> </html>
shiro攔截器樹狀結構如下圖:<!-- 基於Form表單的身份驗證過濾器 --> <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"> <property name="usernameParam" value="username"/> <property name="passwordParam" value="password"/> <property name="loginUrl" value="/login.jsp"/> </bean> <!-- Shiro的Web過濾器:此處使用ShiroFilterFactoryBean來建立ShiroFilter過濾器 --> <!-- Bean id必須和web.xml檔案中配置的DelegatingFilterProxy的filter-name一致 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/login.jsp"/> <property name="unauthorizedUrl" value="/unauthorized.jsp"/> <property name="filters"> <util:map> <entry key="authc" value-ref="formAuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> /index.jsp = anon /unauthorized.jsp = anon /error.jsp = anon /login.jsp = authc /logout = logout /** = user </value> </property> </bean>
其中幾個主要的攔截:OnceperRequestFilter、AbstractShiroFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthorizationFilter等構成shiro的攔截器基本架構。
二、初始化過程
啟動專案時spring首先會通過doGetObjectFromFactoryBean()方法來初始化Shiro的攔截器入口工廠類,即org.apache.shiro.spring.web.ShiroFilterFactoryBean:ShiroFilterFactoryBean提供的獲取例項的工廠方法:/** * Obtain an object to expose from the given FactoryBean. * @param factory the FactoryBean instance * @param beanName the name of the bean * @return the object obtained from the FactoryBean * @throws BeanCreationException if FactoryBean object creation failed * @see org.springframework.beans.factory.FactoryBean#getObject() */ private Object doGetObjectFromFactoryBean(final FactoryBean<?> factory, final String beanName) throws BeanCreationException { Object object; try { if (System.getSecurityManager() != null) { AccessControlContext acc = getAccessControlContext(); try { object = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { return factory.getObject(); } }, acc); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { // 呼叫工廠方法生成例項 object = factory.getObject(); } } catch (FactoryBeanNotInitializedException ex) { throw new BeanCurrentlyInCreationException(beanName, ex.toString()); } catch (Throwable ex) { throw new BeanCreationException(beanName, "FactoryBean threw exception on object creation", ex); } // Do not accept a null value for a FactoryBean that's not fully // initialized yet: Many FactoryBeans just return null then. if (object == null && isSingletonCurrentlyInCreation(beanName)) { throw new BeanCurrentlyInCreationException(beanName, "FactoryBean which is currently in creation returned null from getObject"); } return object; }
/**
* Lazily creates and returns a {@link AbstractShiroFilter} concrete instance via the
* {@link #createInstance} method.
*
* @return the application's Shiro Filter instance used to filter incoming web requests.
* @throws Exception if there is a problem creating the {@code Filter} instance.
*/
public Object getObject() throws Exception {
if (instance == null) {
instance = createInstance();
}
return instance;
}
最終會通過呼叫ShiroFilterFactoryBean的createInstance()方法初始化shiro攔截器入口類:protected AbstractShiroFilter createInstance() throws Exception {
log.debug("Creating Shiro Filter instance.");
// 獲取的是配置的DefaultWebSecurityManager例項
SecurityManager securityManager = getSecurityManager();
if (securityManager == null) {
String msg = "SecurityManager property must be set.";
throw new BeanInitializationException(msg);
}
if (!(securityManager instanceof WebSecurityManager)) {
String msg = "The security manager does not implement the WebSecurityManager interface.";
throw new BeanInitializationException(msg);
}
// 建立負責維護URL模式與攔截器鏈關係的DefaultFilterChainManager例項
FilterChainManager manager = createFilterChainManager();
//Expose the constructed FilterChainManager by first wrapping it in a
// FilterChainResolver implementation. The AbstractShiroFilter implementations
// do not know about FilterChainManagers - only resolvers:
// 建立shiro提供的唯一FilterChainResolver實現,用於解析訪問路徑所對應的攔截器鏈
PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();
chainResolver.setFilterChainManager(manager);
//Now create a concrete ShiroFilter instance and apply the acquired SecurityManager and built
//FilterChainResolver. It doesn't matter that the instance is an anonymous inner class
//here - we're just using it because it is a concrete AbstractShiroFilter instance that accepts
//injection of the SecurityManager and FilterChainResolver:
// 最終返回的是SpringShiroFilter例項(ShiroFilterFactoryBean的內部類)
return new SpringShiroFilter((WebSecurityManager) securityManager, chainResolver);
}
接下來看看shiro在createFilterChainManager()方法如何建立FilterChainManager例項:protected FilterChainManager createFilterChainManager() {
// 建立的是shiro預設的DefaultFilterChainManager例項(構造方法內初始化預設攔截器)
DefaultFilterChainManager manager = new DefaultFilterChainManager();
// 獲取shiro預設的攔截器Map<攔截路徑,攔截器>對映集合
Map<String, Filter> defaultFilters = manager.getFilters();
// Apply global settings if necessary:(為預設攔截器設定通用屬性例如loginUrl,unauthroizedUrl等)
for (Filter filter : defaultFilters.values()) {
applyGlobalPropertiesIfNecessary(filter);
}
// Apply the acquired and/or configured filters:獲取配置檔案中filters屬性配置的攔截器Map集合
Map<String, Filter> filters = getFilters();
// 處理自定義的攔截器
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry<String, Filter> entry : filters.entrySet()) {
String name = entry.getKey();
Filter filter = entry.getValue();
applyGlobalPropertiesIfNecessary(filter);
if (filter instanceof Nameable) {
((Nameable) filter).setName(name);
}
// 'init' argument is false, since Spring-configured filters should be initialized
// in Spring (i.e. 'init-method=blah') or implement InitializingBean:
// 將自定義攔截器加入FilterChainManager中的攔截器Map<攔截器名,攔截器例項>
manager.addFilter(name, filter, false);
}
}
// build up the chains:獲取自定義攔截器鏈Map交由Manager管理(對應的配置檔案屬性是filterChainDefinitions)
Map<String, String> chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry<String, String> entry : chains.entrySet()) {
String url = entry.getKey();// 配置的URL
String chainDefinition = entry.getValue();// 該URL對應的攔截器例項
// 解析filterChainDefinitions配置:每一個配置的URL對應一個shiro代理攔截器鏈
// 並將解析的每一個攔截器鏈交由DefaultFilterChainManager的Map<URL路徑,攔截器鏈>管理
manager.createChain(url, chainDefinition);
}
}
return manager;
}
shiro預設攔截器鏈管理器DefaultFilterChainManager的初始化構造方法:// DefaultFilterChainManager在初始化時新增預設攔截器
public DefaultFilterChainManager() {
// 此處初始化攔截器Map<攔截器名稱,攔截器例項>:這裡儲存所有的預設以及自定義的攔截器
this.filters = new LinkedHashMap<String, Filter>();
// 此處初始化攔截器鏈Map<攔截器鏈名稱(攔截路徑),攔截器集合>:這裡儲存所有自定義的攔截器鏈所對應的攔截器集合
this.filterChains = new LinkedHashMap<String, NamedFilterList>();
addDefaultFilters(false);
}
// DefaultFilter是一個列舉類,定義了shiro的所有預設攔截器
protected void addDefaultFilters(boolean init) {
for (DefaultFilter defaultFilter : DefaultFilter.values()) {
// 將所有預設攔截器加入filters集合中
addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
啟動時解析攔截器鏈定義的set方法:/**
* A convenience method that sets the {@link #setFilterChainDefinitionMap(java.util.Map) filterChainDefinitionMap}
* property by accepting a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs).
* Each key/value pair must conform to the format defined by the
* {@link FilterChainManager#createChain(String,String)} JavaDoc - each property key is an ant URL
* path expression and the value is the comma-delimited chain definition.
*
* @param definitions a {@link java.util.Properties Properties}-compatible string (multi-line key/value pairs)
* where each key/value pair represents a single urlPathExpression-commaDelimitedChainDefinition.
*/
// ShiroFilterFactoryBean的setFilterChainDefinitions方法:解析配置的"filterChainDefinitions"屬性
public void setFilterChainDefinitions(String definitions) {
Ini ini = new Ini();
ini.load(definitions);
//did they explicitly state a 'urls' section? Not necessary, but just in case:
Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
if (CollectionUtils.isEmpty(section)) {
//no urls section. Since this _is_ a urls chain definition property, just assume the
//default section contains only the definitions:
section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
}
// 最後解析為Map<攔截路徑,攔截器鏈定義>並呼叫set方法初始化
setFilterChainDefinitionMap(section);
}
/**
* Sets the chainName-to-chainDefinition map of chain definitions to use for creating filter chains intercepted
* by the Shiro Filter. Each map entry should conform to the format defined by the
* {@link FilterChainManager#createChain(String, String)} JavaDoc, where the map key is the chain name (e.g. URL
* path expression) and the map value is the comma-delimited string chain definition.
*
* @param filterChainDefinitionMap the chainName-to-chainDefinition map of chain definitions to use for creating
* filter chains intercepted by the Shiro Filter.
*/
// ShiroFilterFactoryBean的set方法:初始化攔截器定義Map
public void setFilterChainDefinitionMap(Map<String, String> filterChainDefinitionMap) {
this.filterChainDefinitionMap = filterChainDefinitionMap;
}
Shiro宣告的預設攔截器列舉類:public enum DefaultFilter {
anon(AnonymousFilter.class),
authc(FormAuthenticationFilter.class),
authcBasic(BasicHttpAuthenticationFilter.class),
logout(LogoutFilter.class),
noSessionCreation(NoSessionCreationFilter.class),
perms(PermissionsAuthorizationFilter.class),
port(PortFilter.class),
rest(HttpMethodPermissionFilter.class),
roles(RolesAuthorizationFilter.class),
ssl(SslFilter.class),
user(UserFilter.class);
private final Class<? extends Filter> filterClass;
private DefaultFilter(Class<? extends Filter> filterClass) {
this.filterClass = filterClass;
}
public Filter newInstance() {
return (Filter) ClassUtils.newInstance(this.filterClass);
}
public Class<? extends Filter> getFilterClass() {
return this.filterClass;
}
public static Map<String, Filter> createInstanceMap(FilterConfig config) {
Map<String, Filter> filters = new LinkedHashMap<String, Filter>(values().length);
for (DefaultFilter defaultFilter : values()) {
Filter filter = defaultFilter.newInstance();
if (config != null) {
try {
filter.init(config);
} catch (ServletException e) {
String msg = "Unable to correctly init default filter instance of type " + filter.getClass().getName();
throw new IllegalStateException(msg, e);
}
}
filters.put(defaultFilter.name(), filter);
}
return filters;
}
}
再看Shiro如何通過DefaultFilterChainManager的createChain方法初始化攔截器鏈:
/**
* @param chainName 攔截器鏈名稱,即攔截路徑
* @param chainDefinition 攔截器鏈定義,即配置的以逗號分割的攔截器定義字串
*/
public void createChain(String chainName, String chainDefinition) {
if (!StringUtils.hasText(chainName)) {
throw new NullPointerException("chainName cannot be null or empty.");
}
if (!StringUtils.hasText(chainDefinition)) {
throw new NullPointerException("chainDefinition cannot be null or empty.");
}
if (log.isDebugEnabled()) {
log.debug("Creating chain [" + chainName + "] from String definition [" + chainDefinition + "]");
}
//parse the value by tokenizing it to get the resulting filter-specific config entries
//
//e.g. for a value of
//
// "authc, roles[admin,user], perms[file:edit]"
//
// the resulting token array would equal
//
// { "authc", "roles[admin,user]", "perms[file:edit]" }
//
// 參照以上英文,即以逗號分割解析攔截器鏈定義得到攔截器定義陣列
String[] filterTokens = splitChainDefinition(chainDefinition);
//each token is specific to each filter.
//strip the name and extract any filter-specific config between brackets [ ]
for (String token : filterTokens) {
// 解析攔截器定義(例如authc[bar,baz])得到攔截器名稱及其引數的陣列即:array[0]="authc",array[1]="bar,baz"
String[] nameConfigPair = toNameConfigPair(token);
// now we have the filter name, path and (possibly null) path-specific config. Let's apply them:
// 根據攔截器鏈名稱(攔截URL),攔截器名稱,攔截器引數(可選),初始化攔截器鏈
addToChain(chainName, nameConfigPair[0], nameConfigPair[1]);
}
}
// 初始化的過載方法如下:
public void addToChain(String chainName, String filterName, String chainSpecificFilterConfig) {
if (!StringUtils.hasText(chainName)) {
throw new IllegalArgumentException("chainName cannot be null or empty.");
}
// 從之前初始化的Map攔截器集合中獲取攔截器例項
Filter filter = getFilter(filterName);
if (filter == null) {
throw new IllegalArgumentException("There is no filter with name '" + filterName +
"' to apply to chain [" + chainName + "] in the pool of available Filters. Ensure a " +
"filter with that name/path has first been registered with the addFilter method(s).");
}
// 應用當前攔截器引數配置
applyChainConfig(chainName, filter, chainSpecificFilterConfig);
// 確保攔截器鏈不為NULL,返回的是該攔截器鏈對應的攔截器List集合
NamedFilterList chain = ensureChain(chainName);
// 將當前攔截器加入攔截器List集合
chain.add(filter);
}
Shiro通過DefaultFilterChainManager的applyChainConfig()方法設定各個攔截器的配置引數:// DefaultFilterChainManager的applyChainConfig()方法
protected void applyChainConfig(String chainName, Filter filter, String chainSpecificFilterConfig) {
if (log.isDebugEnabled()) {
log.debug("Attempting to apply path [" + chainName + "] to filter [" + filter + "] " +
"with config [" + chainSpecificFilterConfig + "]");
}
// 判斷當前攔截器是否是PathConfigProcessor型別
// shiro預設的攔截器中除logout外均繼承至PathMatchingFilter,而PathMatchingFilter實現了介面PathConfigProcessor
if (filter instanceof PathConfigProcessor) {
// 呼叫當前攔截器例項的processPathConfig方法處理攔截路徑引數配置
((PathConfigProcessor) filter).processPathConfig(chainName, chainSpecificFilterConfig);
} else {
if (StringUtils.hasText(chainSpecificFilterConfig)) {
// 如果攔截器不屬於PathConfigProcessor型別且配有引數則會丟擲異常
//they specified a filter configuration, but the Filter doesn't implement PathConfigProcessor
//this is an erroneous config:
String msg = "chainSpecificFilterConfig was specified, but the underlying " +
"Filter instance is not an 'instanceof' " +
PathConfigProcessor.class.getName() + ". This is required if the filter is to accept " +
"chain-specific configuration.";
throw new ConfigurationException(msg);
}
}
}
執行攔截器自己的processPathConfig()方法:/**
* Splits any comma-delmited values that might be found in the <code>config</code> argument and sets the resulting
* <code>String[]</code> array on the <code>appliedPaths</code> internal Map.
* <p/>
* That is:
* <pre><code>
* String[] values = null;
* if (config != null) {
* values = split(config);
* }
* <p/>
* this.{@link #appliedPaths appliedPaths}.put(path, values);
* </code></pre>
*
* @param path the application context path to match for executing this filter.
* @param config the specified for <em>this particular filter only</em> for the given <code>path</code>
* @return this configured filter.
*/
// PathMatchingFilter的processPathConfig方法(實現PathConfigProcessor介面的方法)
public Filter processPathConfig(String path, String config) {
String[] values = null;
if (config != null) {
// 按照逗號分隔符繼續分解配置引數
values = split(config);
}
// 將<攔截路徑-配置引數>放入當前攔截器例項的Map集合(繼承至父類PathMatchingFilter的屬性)
// 當以後觸發攔截器時即可根據攔截路徑獲取引數配置進行相關處理,如果沒有配置則values存null
this.appliedPaths.put(path, values);
return this;
}
/**
* A collection of path-to-config entries where the key is a path which this filter should process and
* the value is the (possibly null) configuration element specific to this Filter for that specific path.
* <p/>
* <p>To put it another way, the keys are the paths (urls) that this Filter will process.
* <p>The values are filter-specific data that this Filter should use when processing the corresponding
* key (path). The values can be null if no Filter-specific config was specified for that url.
*/
// PathMatchingFilter的攔截路徑引數配置Map<攔截路徑,配置引數>集合
protected Map<String, Object> appliedPaths = new LinkedHashMap<String, Object>();
保險方法ensureChain()確保攔截器鏈不為空:// DefaultFilterChainManager的ensureChain方法
protected NamedFilterList ensureChain(String chainName) {
// 根據攔截器鏈名稱從攔截器鏈Map集合中獲取對應的攔截器集合
NamedFilterList chain = getChain(chainName);
if (chain == null) {
// 如果不存在則進行初始化
chain = new SimpleNamedFilterList(chainName);
this.filterChains.put(chainName, chain);
}
return chain;
}
public NamedFilterList getChain(String chainName) {
return this.filterChains.get(chainName);
}
三、攔截過程淺析
這裡首先以訪問工程路徑為例,即http://127.0.0.1:8082/shiro02。在debug模式下,首先進入到攔截器的doFilter()方法,實際上這個攔截器例項就是上面提到的createInstance()方法建立的SpringShiroFilter攔截器例項,這個類繼承至AbstractShiroFilter,是ShiroFilterFactoryBean的內部類,具體請參考上面的類結構圖。/**
* This {@code doFilter} implementation stores a request attribute for
* "already filtered", proceeding without filtering again if the
* attribute is already there.
*
* @see #getAlreadyFilteredAttributeName
* @see #shouldNotFilter
* @see #doFilterInternal
*/
// 當前進入的攔截方法是其父類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);
// 判斷當前攔截器是否已經開啟(配置檔案可配,預設開啟) (shouldNotFilter()方法已廢棄返回值固定為false)
} 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 {
// 執行過濾邏輯,這裡呼叫的是其父類AbstractShiroFilter的方法
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);
}
}
}
下面進入doFilterInternal()方法:/**
* {@code doFilterInternal} implementation that sets-up, executes, and cleans-up a Shiro-filtered request. It
* performs the following ordered operations:
* <ol>
* <li>{@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
* the incoming {@code ServletRequest} for use during Shiro's processing</li>
* <li>{@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
* the outgoing {@code ServletResponse} for use during Shiro's processing</li>
* <li> {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
* {@link Subject} instance based on the specified request/response pair.</li>
* <li>Finally {@link Subject#execute(Runnable) executes} the
* {@link #updateSessionLastAccessTime(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} and
* {@link #executeChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)}
* methods</li>
* </ol>
* <p/>
* The {@code Subject.}{@link Subject#execute(Runnable) execute(Runnable)} call in step #4 is used as an
* implementation technique to guarantee proper thread binding and restoration is completed successfully.
*
* @param servletRequest the incoming {@code ServletRequest}
* @param servletResponse the outgoing {@code ServletResponse}
* @param chain the container-provided {@code FilterChain} to execute
* @throws IOException if an IO error occurs
* @throws javax.servlet.ServletException if an Throwable other than an IOException
*/
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的doFilterInternal方法
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException {
Throwable t = null;
try {
// Shiro在此對HttpServletRequest、HttpServletResponse進行封裝
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
// 建立subject例項
final Subject subject = createSubject(request, response);
// noinspection unchecked
// 執行回撥,最終執行的是下面的call()方法
subject.execute(new Callable() {
public Object call() throws Exception {
// 更新session最後活動時間
updateSessionLastAccessTime(request, response);
// 執行攔截器鏈
executeChain(request, response, chain);
return null;
}
});
} catch (ExecutionException ex) {
t = ex.getCause();
} catch (Throwable throwable) {
t = throwable;
}
if (t != null) {
if (t instanceof ServletException) {
throw (ServletException) t;
}
if (t instanceof IOException) {
throw (IOException) t;
}
//otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one:
String msg = "Filtered request failed.";
throw new ServletException(msg, t);
}
}
/**
* Executes a {@link FilterChain} for the given request.
* <p/>
* This implementation first delegates to
* <code>{@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}</code>
* to allow the application's Shiro configuration to determine exactly how the chain should execute. The resulting
* value from that call is then executed directly by calling the returned {@code FilterChain}'s
* {@link FilterChain#doFilter doFilter} method. That is:
* <pre>
* FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
* chain.{@link FilterChain#doFilter doFilter}(request,response);</pre>
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param origChain the Servlet Container-provided chain that may be wrapped further by an application-configured
* chain of Filters.
* @throws IOException if the underlying {@code chain.doFilter} call results in an IOException
* @throws ServletException if the underlying {@code chain.doFilter} call results in a ServletException
* @since 1.0
*/
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的executeChain方法
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
// 獲取當前請求對應的攔截器鏈
FilterChain chain = getExecutionChain(request, response, origChain);
// 執行攔截器鏈
chain.doFilter(request, response);
}
/**
* Returns the {@code FilterChain} to execute for the given request.
* <p/>
* The {@code origChain} argument is the
* original {@code FilterChain} supplied by the Servlet Container, but it may be modified to provide
* more behavior by pre-pending further chains according to the Shiro configuration.
* <p/>
* This implementation returns the chain that will actually be executed by acquiring the chain from a
* {@link #getFilterChainResolver() filterChainResolver}. The resolver determines exactly which chain to
* execute, typically based on URL configuration. If no chain is returned from the resolver call
* (returns {@code null}), then the {@code origChain} will be returned by default.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param origChain the original {@code FilterChain} provided by the Servlet Container
* @return the {@link FilterChain} to execute for the given request
* @since 1.0
*/
// 當前為SpringShiroFilter攔截器的父類AbstractShiroFilter的getExecuteChain方法
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {
FilterChain chain = origChain;
// 獲取預設的攔截器鏈解析例項:即初始化時設定的PathMatchingFilterChainResolver例項
FilterChainResolver resolver = getFilterChainResolver();
if (resolver == null) {
log.debug("No FilterChainResolver configured. Returning original FilterChain.");
return origChain;
}
// 通過PathMatchingFilterChainResolver解析當前訪問路徑獲取對應的攔截器鏈
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;
}
接下來看看Shiro如何解析並獲得指定的攔截器鏈:// 當前方法為PathMatchingFilterChainResolver的getChain方法
public FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain) {
// 獲取攔截器鏈管理器例項:即初始化時設定的DefaultFilterChainManager例項
FilterChainManager filterChainManager = getFilterChainManager();
if (!filterChainManager.hasChains()) {
return null;
}
// 獲取當前請求訪問的URI路徑
String requestURI = getPathWithinApplication(request);
//the 'chain names' in this implementation are actually path patterns defined by the user. We just use them
//as the chain name for the FilterChainManager's requirements
// 遍歷攔截器鏈管理器中所有的攔截器鏈
for (String pathPattern : filterChainManager.getChainNames()) {
// If the path does match, then pass on to the subclass implementation for specific checks:
if (pathMatches(pathPattern, requestURI)) {
if (log.isTraceEnabled()) {
log.trace("Matched path pattern [" + pathPattern + "] for requestURI [" + requestURI + "]. " +
"Utilizing corresponding filter chain...");
}
// 如果當前訪問URI路徑與配置的攔截器鏈的攔截路徑匹配則返回代理後的攔截器鏈
return filterChainManager.proxy(originalChain, pathPattern);
}
}
return null;
}
再來看Shiro如何對攔截器鏈進行代理:/**
* @param original 原始攔截器鏈
* @param chainName 攔截器鏈名稱(實際就是攔截路徑)
*/
// 當前為DefaultFilterChainManager的proxy方法
public FilterChain proxy(FilterChain original, String chainName) {
// 獲取指定攔截路徑對應的攔截器集合
NamedFilterList configured = getChain(chainName);
if (configured == null) {
String msg = "There is no configured chain under the name/key [" + chainName + "].";
throw new IllegalArgumentException(msg);
}
// 返回代理後的攔截器鏈
return configured.proxy(original);
}
// DefaultFilterChainManager的getChain方法
public NamedFilterList getChain(String chainName) {
// filterChains就是一個Map<String, NamedFilterList>屬性
// NamedFilterList是Shiro存放攔截器鏈的繼承List介面的集合
return this.filterChains.get(chainName);
}
NamedFilterList介面聲明瞭2個方法:getName()和proxy() 如下:/**
* A {@code NamedFilterList} is a {@code List} of {@code Filter} instances that is uniquely identified by a
* {@link #getName() name}. It has the ability to generate new {@link FilterChain} instances reflecting this list's
* filter order via the {@link #proxy proxy} method.
*
* @since 1.0
*/
public interface NamedFilterList extends List<Filter> {
/**
* Returns the configuration-unique name assigned to this {@code Filter} list.
*
* @return the configuration-unique name assigned to this {@code Filter} list.
*/
String getName();
/**
* Returns a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
* and end with the execution of the given {@code filterChain} instance.
*
* @param filterChain the {@code FilterChain} instance to execute after this list's {@code Filter}s have executed.
* @return a new {@code FilterChain} instance that will first execute this list's {@code Filter}s (in list order)
* and end with the execution of the given {@code filterChain} instance.
*/
FilterChain proxy(FilterChain filterChain);
}
Shiro提供了NamedFilterList介面的通用實現SimpleNamedFilterList,所以最後實際呼叫的是該類的proxy()方法:/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.web.filter.mgt;
import org.apache.shiro.util.StringUtils;
import org.apache.shiro.web.servlet.ProxiedFilterChain;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import java.util.*;
/**
* Simple {@code NamedFilterList} implementation that is supported by a backing {@link List} instance and a simple
* {@link #getName() name} property. All {@link List} method implementations are immediately delegated to the
* wrapped backing list.
*
* @since 1.0
*/
public class SimpleNamedFilterList implements NamedFilterList {
private String name;
private List<Filter> backingList;
/**
* Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name}, defaulting to a new
* {@link ArrayList ArrayList} instance as the backing list.
*
* @param name the name to assign to this instance.
* @throws IllegalArgumentException if {@code name} is null or empty.
*/
public SimpleNamedFilterList(String name) {
this(name, new ArrayList<Filter>());
}
/**
* Creates a new {@code SimpleNamedFilterList} instance with the specified {@code name} and {@code backingList}.
*
* @param name the name to assign to this instance.
* @param backingList the list instance used to back all of this class's {@link List} method implementations.
* @throws IllegalArgumentException if {@code name} is null or empty.
* @throws NullPointerException if the backing list is {@code null}.
*/
public SimpleNamedFilterList(String name, List<Filter> backingList) {
if (backingList == null) {
throw new NullPointerException("backingList constructor argument cannot be null.");
}
this.backingList = backingList;
setName(name);
}
protected void setName(String name) {
if (!StringUtils.hasText(name)) {
throw new IllegalArgumentException("Cannot specify a null or empty name.");
}
this.name = name;
}
public String getName() {
return name;
}
// 具體代理攔截器鏈的方法
public FilterChain proxy(FilterChain orig) {
return new ProxiedFilterChain(orig, this);
}
public boolean add(Filter filter) {
return this.backingList.add(filter);
}
public void add(int index, Filter filter) {
this.backingList.add(index, filter);
}
public boolean addAll(Collection<? extends Filter> c) {
return this.backingList.addAll(c);
}
public boolean addAll(int index, Collection<? extends Filter> c) {
return this.backingList.addAll(index, c);
}
public void clear() {
this.backingList.clear();
}
public boolean contains(Object o) {
return this.backingList.contains(o);
}
public boolean containsAll(Collection<?> c) {
return this.backingList.containsAll(c);
}
public Filter get(int index) {
return this.backingList.get(index);
}
public int indexOf(Object o) {
return this.backingList.indexOf(o);
}
public boolean isEmpty() {
return this.backingList.isEmpty();
}
public Iterator<Filter> iterator() {
return this.backingList.iterator();
}
public int lastIndexOf(Object o) {
return this.backingList.lastIndexOf(o);
}
public ListIterator<Filter> listIterator() {
return this.backingList.listIterator();
}
public ListIterator<Filter> listIterator(int index) {
return this.backingList.listIterator(index);
}
public Filter remove(int index) {
return this.backingList.remove(index);
}
public boolean remove(Object o) {
return this.backingList.remove(o);
}
public boolean removeAll(Collection<?> c) {
return this.backingList.removeAll(c);
}
public boolean retainAll(Collection<?> c) {
return this.backingList.retainAll(c);
}
public Filter set(int index, Filter filter) {
return this.backingList.set(index, filter);
}
public int size() {
return this.backingList.size();
}
public List<Filter> subList(int fromIndex, int toIndex) {
return this.backingList.subList(fromIndex, toIndex);
}
public Object[] toArray() {
return this.backingList.toArray();
}
public <T> T[] toArray(T[] a) {
//noinspection SuspiciousToArrayCall
return this.backingList.toArray(a);
}
}
可以看到執行proxy()方法後實際返回的是Shiro提供的ProxiedFilterChain例項,該類如下:/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.shiro.web.servlet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import java.io.IOException;
import java.util.List;
/**
* A proxied filter chain is a {@link FilterChain} instance that proxies an original {@link FilterChain} as well
* as a {@link List List} of other {@link Filter Filter}s that might need to execute prior to the final wrapped
* original chain. It allows a list of filters to execute before continuing the original (proxied)
* {@code FilterChain} instance.
*
* @since 0.9
*/
public class ProxiedFilterChain implements FilterChain {
//TODO - complete JavaDoc
private static final Logger log = LoggerFactory.getLogger(ProxiedFilterChain.class);
private FilterChain orig;// 原始攔截器鏈
private List<Filter> filters;// 原始攔截器鏈對應的攔截器集合
private int index = 0;// 當前攔截器索引
// 構造攔截器鏈代理例項
public ProxiedFilterChain(FilterChain orig, List<Filter> filters) {
if (orig == null) {
throw new NullPointerException("original FilterChain cannot be null.");
}
this.orig = orig;
this.filters = filters;
this.index = 0;
}
// 這個方法在攔截器鏈執行過程中被遞迴呼叫
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.filters == null || this.filters.size() == this.index) {
//we've reached the end of the wrapped chain, so invoke the original one:
if (log.isTraceEnabled()) {
log.trace("Invoking original filter chain.");
}
this.orig.doFilter(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Invoking wrapped filter at index [" + this.index + "]");
}
this.filters.get(this.index++).doFilter(request, response, this);
}
}
}
迴歸主線,在過取代理攔截器鏈後,返回executeChain方法,開始執行攔截過濾:chain.doFilter(),這個chain既然是代理攔截器鏈,呼叫的doFilter方法就是上面提到的ProxiedFilterChain的doFilter方法。這裡的filters對應的就是當前攔截器鏈對應的攔截器集合,在DefaultFilterChainManager中的getChain方法中設定,DefaultFilterChainManager的攔截器對映集合在解析配置檔案時進行初始化。由於訪問路徑為"http://127.0.0.1:8080/shiro02/",則匹配的攔截器鏈就是配置的"/**=user",所以當前攔截器鏈的攔截器集合中只有一個攔截器:UserFilter,執行如下程式碼即遍歷攔截器集合遞迴呼叫攔截器的攔截方法:this.filters.get(this.index++).doFilter(request, response, this);
首先呼叫UserFilter的doFilter()方法,由Shiro攔截器類繼承結構圖可知,這裡呼叫的實際是其父類OncePerRequestFilter的doFilter方法,又好像回到了初次執行SpringShiroFilter的doFilter()方法的時候,具體程式碼參考以上。首先判斷如果沒有執行過並且設定開啟攔截,才執行doFilterInternal()方法,然後標記為執行過,一次請求完成後清除是否已執行標記。不同的是UserFilter呼叫的是其父類AdviceFilter的doFilterInternal()方法。/**
* Actually implements the chain execution logic, utilizing
* {@link #preHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) pre},
* {@link #postHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse) post}, and
* {@link #afterCompletion(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Exception) after}
* advice hooks.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @param chain the filter chain to execute
* @throws ServletException if a servlet-related error occurs
* @throws IOException if an IO error occurs
*/
// AdviceFilter的doFilterInternal()方法
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException {
Exception exception = null;
try {
// 前置處理:這裡呼叫的是其父類PathMatchingFilter重寫的perHandle()
boolean continueChain = preHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Invoked preHandle method. Continuing chain?: [" + continueChain + "]");
}
// 判斷是否執行攔截器鏈的下一個攔截器
if (continueChain) {
executeChain(request, response, chain);
}
// 後置處理:呼叫的是AdviceFilter的postHandle()(無具體實現)
postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
}
} catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}
/**
* Implementation that handles path-matching behavior before a request is evaluated. If the path matches and
* the filter
* {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String, Object) isEnabled} for
* that path/config, the request will be allowed through via the result from
* {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle}. If the
* path does not match or the filter is not enabled for that path, this filter will allow passthrough immediately
* to allow the {@code FilterChain} to continue executing.
* <p/>
* In order to retain path-matching functionality, subclasses should not override this method if at all
* possible, and instead override
* {@link #onPreHandle(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) onPreHandle} instead.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse
* @return {@code true} if the filter chain is allowed to continue to execute, {@code false} if a subclass has
* handled the request explicitly.
* @throws Exception if an error occurs
*/
// PathMatchingFilter的perHandle()方法
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
// 這裡判斷攔截鏈初始化時為當前攔截器(UserFilter)初始化的Map<攔截路徑,配置引數>是否為NULL
if (this.appliedPaths == null || this.appliedPaths.isEmpty()) {
if (log.isTraceEnabled()) {
log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately.");
}
return true;
}
// 遍歷當前攔截器(UserFilter)的Map<攔截路徑,配置引數>集合
for (String path : this.appliedPaths.keySet()) {
// If the path does match, then pass on to the subclass implementation for specific checks
//(first match 'wins'):
// 如果能夠匹配到當前訪問的路徑則進行攔截處理
if (pathsMatch(path, request)) {
log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path);
// 這裡獲取當前攔截器為該攔截路徑配置的攔截器引數(沒有為null)
Object config = this.appliedPaths.get(path);
// 返回給isFilterChainContinued()方法處理
return isFilterChainContinued(request, response, path, config);
}
}
//no path matched, allow the request to go through: 當前攔截器沒有匹配則放行到下一個攔截器
return true;
}
/**
* Simple method to abstract out logic from the preHandle implementation - it was getting a bit unruly.
*
* @since 1.2
*/
// PathMatchingFilter的isFilterChainContinued()方法
@SuppressWarnings({"JavaDoc"})
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response,
String path, Object pathConfig) throws Exception {
// 這裡呼叫的是PathMatchingFilter的isEnabled:判斷當前攔截器是否開啟
// 最後呼叫的是父類OncePerRequestFilter的isEnabled()方法,判斷屬性enabled==true/false(可配置)
// 子類可重寫此方法,根據攔截器配置引數決定是否開啟當前攔截器
if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is enabled for the current request under path '{}' with config [{}]. " +
"Delegating to subclass implementation for 'onPreHandle' check.",
new Object[]{getName(), path, pathConfig});
}
//The filter is enabled for this specific request, so delegate to subclass implementations
//so they can decide if the request should continue through the chain or not:
// 這裡又返回給子類讓其自己去處理,呼叫的是AccessControlFilter的onPreHandle方法
return onPreHandle(request, response, pathConfig);
}
if (log.isTraceEnabled()) {
log.trace("Filter '{}' is disabled for the current request under path '{}' with config [{}]. " +
"The next element in the FilterChain will be called immediately.",
new Object[]{getName(), path, pathConfig});
}
//This filter is disabled for this specific request,
//return 'true' immediately to indicate that the filter will not process the request
//and let the request/response to continue through the filter chain:
// 當前攔截器沒有開啟執行攔截器鏈的下一個攔截器
return true;
}
/**
* Path-matching version of the parent class's
* {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)} method, but additionally allows
* for inspection of any path-specific configuration values corresponding to the specified request. Subclasses
* may wish to inspect this additional mapped configuration to determine if the filter is enabled or not.
* <p/>
* This method's default implementation ignores the {@code path} and {@code mappedValue} arguments and merely
* returns the value from a call to {@link #isEnabled(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
* It is expected that subclasses override this method if they need to perform enable/disable logic for a specific
* request based on any path-specific config for the filter instance.
*
* @param request the incoming servlet request
* @param response the outbound servlet response
* @param path the path matched for the incoming servlet request that has been configured with the given {@code mappedValue}.
* @param mappedValue the filter-specific config value mapped to this filter in the URL rules mappings for the given {@code path}.
* @return {@code true} if this filter should filter the specified request, {@code false} if it should let the
* request/response pass through immediately to the next element in the {@code FilterChain}.
* @throws Exception in the case of any error
* @since 1.2
*/
// PathMatchingFilter的isEnabled()方法
@SuppressWarnings({"UnusedParameters"})
protected boolean isEnabled(ServletRequest request, ServletResponse response, String path, Object mappedValue)
throws Exception {
// 這裡呼叫其父類OncePerRequestFilter的isEnabled()方法
return isEnabled(request, response);
}
/**
* Returns <code>true</code> if
* {@link #isAccessAllowed(ServletRequest,ServletResponse,Object) isAccessAllowed(Request,Response,Object)},
* otherwise returns the result of
* {@link #onAccessDenied(ServletRequest,ServletResponse,Object) onAccessDenied(Request,Response,Object)}.
*
* @return <code>true</code> if
* {@link #isAccessAllowed(javax.servlet.ServletRequest, javax.servlet.ServletResponse, Object) isAccessAllowed},
* otherwise returns the result of
* {@link #onAccessDenied(javax.servlet.ServletRequest, javax.servlet.ServletResponse) onAccessDenied}.
* @throws Exception if an error occurs.
*/
// AccessControlFilter的onPreHandle方法
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
// 方法isAccessAllowed()和onAccessDenied()在AccessControlFilter中均是抽象方法
// 這裡呼叫的是子類實現的方法isAccessAllowed()和onAccessDenied()
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
分析到這,其實最終呼叫了子類(當前為UserFilter)實現的isAccessAllowed() || onAccessDenied() 的結果,UserFilter原始碼:/**
* Filter that allows access to resources if the accessor is a known user, which is defined as
* having a known principal. This means that any user who is authenticated or remembered via a
* 'remember me' feature will be allowed access from this filter.
* <p/>
* If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}</p>
*
* @since 0.9
*/
public class UserFilter extends AccessControlFilter {
/**
* Returns <code>true</code> if the request is a
* {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
* if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
* is not <code>null</code>, <code>false</code> otherwise.
*
* @return <code>true</code> if the request is a
* {@link #isLoginRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse) loginRequest} or
* if the current {@link #getSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) subject}
* is not <code>null</code>, <code>false</code> otherwise.
*/
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 如果是登入請求則允許通過:呼叫的是其父類AccessControlFilter的isLoginRequest()方法
if (isLoginRequest(request, response)) {
return true;
} else {
// 注意:如果session超時,此處獲取subject物件時會丟擲UnknownSessionException
Subject subject = getSubject(request, response);
// If principal is not null, then the user is known and should be allowed access.
// 否則判斷使用者是否已經登入(Rememer me)
return subject.getPrincipal() != null;
}
}
/**
* This default implementation simply calls
* {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) saveRequestAndRedirectToLogin}
* and then immediately returns <code>false</code>, thereby preventing the chain from continuing so the redirect may
* execute.
*/
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 呼叫父類AccessControlFilter的saveRequestAndRedirectToLogin方法儲存被拒絕的請求並重定向到登入頁,登入成功後再執行
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
顯然isAccessAllowed()方法返回結果為false,然後執行onAccessDenied()方法,這裡呼叫了父類AccessControlFilter的saveRequestAndRedirectToLogin方法,用於儲存被攔截的請求待登入成功後執行。/**
* Convenience method for subclasses to use when a login redirect is required.
* <p/>
* This implementation simply calls {@link #saveRequest(javax.servlet.ServletRequest) saveRequest(request)}
* and then {@link #redirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse) redirectToLogin(request,response)}.
*
* @param request the incoming <code>ServletRequest</code>
* @param response the outgoing <code>ServletResponse</code>
* @throws IOException if an error occurs.
*/
// AccessControlFilter類的saveRequestAndRedirectToLogin方法
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}
/**
* Convenience method merely delegates to
* {@link WebUtils#saveRequest(javax.servlet.ServletRequest) WebUtils.saveRequest(request)} to save the request
* state for reuse later. This is mostly used to retain user request state when a redirect is issued to
* return the user to their originally requested url/resource.
* <p/>
* If you need to save and then immediately redirect the user to login, consider using
* {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
* saveRequestAndRedirectToLogin(request,response)} directly.
*
* @param request the incoming ServletRequest to save for re-use later (for example, after a redirect).
*/
// AccessControlFilter類的saveRequest方法
protected void saveRequest(ServletRequest request) {
// 儲存請求
WebUtils.saveRequest(request);
}
/**
* Convenience method for subclasses that merely acquires the {@link #getLoginUrl() getLoginUrl} and redirects
* the request to that url.
* <p/>
* <b>N.B.</b> If you want to issue a redirect with the intention of allowing the user to then return to their
* originally requested URL, don't use this method directly. Instead you should call
* {@link #saveRequestAndRedirectToLogin(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
* saveRequestAndRedirectToLogin(request,response)}, which will save the current request state so that it can
* be reconstructed and re-used after a successful login.
*
* @param request the incoming <code>ServletRequest</code>
* @param response the outgoing <code>ServletResponse</code>
* @throws IOException if an error occurs.
*/
// AccessControlFilter類的redirectToLogin方法
protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
// 配置的重定向的登入地址
String loginUrl = getLoginUrl();
// 重定向到loginUrl
WebUtils.issueRedirect(request, response, loginUrl);
}
請求重定向後,返回結果為return false,最後返回到AdviceFilter的doFilterInternal()方法,告訴攔截器鏈不再繼續執行,本次請求攔截處理結束。具體攔截過程是巢狀呼叫的,即:SpringShiroFilter intercept start ..匹配當前請求路徑的攔截器鏈開始執行..UserFilter intercept start ..if(userFilter.doFilter return true ){ nextFilter intercept start ..if(nextFilter.doFilter retrun true)...nextFilter intercpt end ..
}else { 終止攔截器執行!!
}UserFilter intercept end ..
匹配當前請求路徑的攔截器鏈結束執行..SpringShiroFilter intercept end ..由於進行了請求重定向,地址:http://127.0.0.1:8080/shiro02/login.jsp,此時會再次進入shiro攔截器鏈,根據以上配置/login.jsp對應的攔截器鏈為:authc(表單登入攔截器),首先執行FormAuthenticationFilter的doFilter()方法(繼承至父類OncePerRequestFilter的方法),再執行父類AdviceFilter的doFilterInternal方法,與上面提到的UserFilter類似。最終同樣會通過父類AccessControlFilter的onPreHandle()方法類決定是否繼續攔截器鏈的執行:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
只不過isAccessAllowed()與onAccessDenied()方法的具體實現與UserFilter的有所不同,isAccessAllowed()呼叫的是FormAuthenticationFilter其父類AuthenticatingFilter的實現,而onAccessDenied()則是FormAuthenticationFilter自己實現:/**
* Determines whether the current subject should be allowed to make the current request.
* <p/>
* The default implementation returns <code>true</code> if the user is authenticated. Will also return
* <code>true</code> if the {@link #isLoginRequest} returns false and the "permissive" flag is set.
*
* @return <code>true</code> if request should be allowed access
*/
// AuthenticatingFilter的isAccessAllowed方法
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
// 首先呼叫父類isAccessAllowed方法判斷是否已經登入認證
// 再判斷如果是非登入請求並且攔截器配置為不攔截所有請求
return super.isAccessAllowed(request, response, mappedValue) ||
(!isLoginRequest(re