登录页
${shiroLoginFailure}
shiro拦截器部分配置:
/index.jsp = anon
/unauthorized.jsp = anon
/error.jsp = anon
/login.jsp = authc
/logout = logout
/** = user
shiro拦截器树状结构如下图:
其中几个主要的拦截:OnceperRequestFilter、AbstractShiroFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthorizationFilter等构成shiro的拦截器基本架构。
(-- 关于各shiro各个拦截器的简单介绍可参考博文:Apache Shiro学习笔记(六)Shiro Filter介绍)
/**
* 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
/**
* 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;
}
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);
}
protected FilterChainManager createFilterChainManager() {
// 创建的是shiro默认的DefaultFilterChainManager实例(构造方法内初始化默认拦截器)
DefaultFilterChainManager manager = new DefaultFilterChainManager();
// 获取shiro默认的拦截器Map<拦截路径,拦截器>映射集合
Map 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 filters = getFilters();
// 处理自定义的拦截器
if (!CollectionUtils.isEmpty(filters)) {
for (Map.Entry 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 chains = getFilterChainDefinitionMap();
if (!CollectionUtils.isEmpty(chains)) {
for (Map.Entry entry : chains.entrySet()) {
String url = entry.getKey();// 配置的URL
String chainDefinition = entry.getValue();// 该URL对应的拦截器实例
// 解析filterChainDefinitions配置:每一个配置的URL对应一个shiro代理拦截器链
// 并将解析的每一个拦截器链交由DefaultFilterChainManager的Map管理
manager.createChain(url, chainDefinition);
}
}
return manager;
}
// DefaultFilterChainManager在初始化时添加默认拦截器
public DefaultFilterChainManager() {
// 此处初始化拦截器Map<拦截器名称,拦截器实例>:这里保存所有的默认以及自定义的拦截器
this.filters = new LinkedHashMap();
// 此处初始化拦截器链Map<拦截器链名称(拦截路径),拦截器集合>:这里保存所有自定义的拦截器链所对应的拦截器集合
this.filterChains = new LinkedHashMap();
addDefaultFilters(false);
}
// DefaultFilter是一个枚举类,定义了shiro的所有默认拦截器
protected void addDefaultFilters(boolean init) {
for (DefaultFilter defaultFilter : DefaultFilter.values()) {
// 将所有默认拦截器加入filters集合中
addFilter(defaultFilter.name(), defaultFilter.newInstance(), init, false);
}
}
/**
* 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 filterChainDefinitionMap) {
this.filterChainDefinitionMap = filterChainDefinitionMap;
}
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 createInstanceMap(FilterConfig config) {
Map filters = new LinkedHashMap(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);
}
// 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);
}
}
}
/**
* Splits any comma-delmited values that might be found in the config
argument and sets the resulting
* String[]
array on the appliedPaths
internal Map.
*
* That is:
*
* String[] values = null;
* if (config != null) {
* values = split(config);
* }
*
* this.{@link #appliedPaths appliedPaths}.put(path, values);
*
*
* @param path the application context path to match for executing this filter.
* @param config the specified for this particular filter only for the given path
* @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.
*
* To put it another way, the keys are the paths (urls) that this Filter will process.
*
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 appliedPaths = new LinkedHashMap();
// 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);
}
/**
* 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:
*
* - {@link #prepareServletRequest(ServletRequest, ServletResponse, FilterChain) Prepares}
* the incoming {@code ServletRequest} for use during Shiro's processing
* - {@link #prepareServletResponse(ServletRequest, ServletResponse, FilterChain) Prepares}
* the outgoing {@code ServletResponse} for use during Shiro's processing
* - {@link #createSubject(javax.servlet.ServletRequest, javax.servlet.ServletResponse) Creates} a
* {@link Subject} instance based on the specified request/response pair.
* - 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
*
*
* 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.
*
* This implementation first delegates to
* {@link #getExecutionChain(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) getExecutionChain}
* 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:
*
* FilterChain chain = {@link #getExecutionChain}(request, response, origChain);
* chain.{@link FilterChain#doFilter doFilter}(request,response);
*
* @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.
*
* 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.
*
* 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属性
// 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 {
/**
* 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 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());
}
/**
* 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 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 iterator() {
return this.backingList.iterator();
}
public int lastIndexOf(Object o) {
return this.backingList.lastIndexOf(o);
}
public ListIterator listIterator() {
return this.backingList.listIterator();
}
public ListIterator 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 subList(int fromIndex, int toIndex) {
return this.backingList.subList(fromIndex, toIndex);
}
public Object[] toArray() {
return this.backingList.toArray();
}
public 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 filters;// 原始拦截器链对应的拦截器集合
private int index = 0;// 当前拦截器索引
// 构造拦截器链代理实例
public ProxiedFilterChain(FilterChain orig, List 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.
*
* 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.
*
* 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 true
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 true
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.
*
* If the accessor is not a known user, then they will be redirected to the {@link #setLoginUrl(String) loginUrl}
*
* @since 0.9
*/
public class UserFilter extends AccessControlFilter {
/**
* Returns true
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 null
, false
otherwise.
*
* @return true
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 null
, false
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 false
, 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.
*
* 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 ServletRequest
* @param response the outgoing ServletResponse
* @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.
*
* 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.
*
* N.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 ServletRequest
* @param response the outgoing ServletResponse
* @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()方法,告诉拦截器链不再继续执行,本次请求拦截处理结束。具体拦截过程是嵌套调用的,即:
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
/**
* Determines whether the current subject should be allowed to make the current request.
*
* The default implementation returns true
if the user is authenticated. Will also return
* true
if the {@link #isLoginRequest} returns false and the "permissive" flag is set.
*
* @return true
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(request, response) && isPermissive(mappedValue));
}
/**
* Returns true
if the mappedValue contains the {@link #PERMISSIVE} qualifier.
*
* @return true
if this filter should be permissive
*/
// AuthenticatingFilter的isPermissive方法
protected boolean isPermissive(Object mappedValue) {
if(mappedValue != null) {
String[] values = (String[]) mappedValue;
return Arrays.binarySearch(values, PERMISSIVE) >= 0;
}
return false;
}
// FormAuthenticationFilter的onAccessDenied()方法
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
// 判断如果是登录请求
if (isLoginRequest(request, response)) {
// 判断如果是POST请求则执行表单登录操作
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
// 调用父类AuthenticatingFilter的executeLogin
return executeLogin(request, response);
// 如果是GET请求则不进行拦截即显示登录页
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
// 调用父类AccessControlFilter的saveRequestAndRedirectToLogin方法保存请求
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
/**
* This default implementation merely returns true
if the request is an HTTP POST
,
* false
otherwise. Can be overridden by subclasses for custom login submission detection behavior.
*
* @param request the incoming ServletRequest
* @param response the outgoing ServletResponse.
* @return true
if the request is an HTTP POST
, false
otherwise.
*/
// FormAuthenticationFilter的isLoginSubmission方法
@SuppressWarnings({"UnusedDeclaration"})
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
}
由此可知,isAccessAllowed返回false,onAccessDenied返回true,最终拦截器链返回结果为true,即浏览器显示登录页login.jsp,后续执行流程与上面UserFilter一致。
由于是POST请求访问的loginUrl,则在表单拦截器的onAccessDenied()方法中会执行父类的executeLogin()进行登录:
// AuthenticatingFilter的executeLogin方法
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
// 调用FormAuthenticationFilter的创建登录请求token方法
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
"must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
// 交给securityManager进行登录处理
subject.login(token);
// FormAuthenticationFilter重写的登录成功回调方法
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
// FormAuthenticationFilter重写的登录失败回调方法
return onLoginFailure(token, e, request, response);
}
}
// FormAuthenticationFilter重写的onLoginSuccess方法
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
// 登录成功重定向(调用父类AuthenticationFilter的issueSuccessRedirect方法)
issueSuccessRedirect(request, response);
//we handled the success redirect directly, prevent the chain from continuing:
return false;
}
// FormAuthenticationFilter重写的onLoginFailure方法
protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
ServletRequest request, ServletResponse response) {
if (log.isDebugEnabled()) {
log.debug( "Authentication exception", e );
}
// 保存登录失败信息,可在jsp使用EL表达式获取
setFailureAttribute(request, e);
//login failed, let request continue back to the login page:
return true;
}
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
String className = ae.getClass().getName();
// 保存键值对(键默认为"shiroLoginFailure")
request.setAttribute(getFailureKeyAttribute(), className);
}
/**
* Redirects to user to the previously attempted URL after a successful login. This implementation simply calls
* {@link org.apache.shiro.web.util.WebUtils WebUtils}.{@link WebUtils#redirectToSavedRequest(javax.servlet.ServletRequest, javax.servlet.ServletResponse, String) redirectToSavedRequest}
* using the {@link #getSuccessUrl() successUrl} as the {@code fallbackUrl} argument to that call.
*
* @param request the incoming request
* @param response the outgoing response
* @throws Exception if there is a problem redirecting.
*/
// AuthenticationFilter的issueSuccessRedirect方法
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
// 重定向到之前保存的请求地址如果没有则重定向到配置的successUrl(默认为"/", 可配置)
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}
这样,登录成功就会终止拦截器链的执行重定向到successUrl,登录失败就继续拦截器链的执行返回到登录页。本文测试工程没有配置successUrl,所以登录成功后会重定向到"/",此时再次进入shiro拦截器链"/**=user",即只会执行UserFilter拦截器,在UserFilter的isAccessAllowed中会判断是否已登录认证,很明显结果为true(刚刚认证成功),继续执行shiro代理拦截器链(如果还有shiro拦截器)以及其他拦截器链(spring、tomcat等),这里将执行springMVC的拦截器,测试工程在springMVC配置文件配置了访问路径"/"跳转到"index.jsp",故最终跳转到index.jsp完成登录请求。springMVC配置如下: