一、环境准备
搭建好spring + shiro整合环境(本文环境Spring 4.3.10.RELEASE + Shiro 1.4.0)后,编写登录页面如下:
<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>
shiro拦截器树状结构如下图:
其中几个主要的拦截:OnceperRequestFilter、AbstractShiroFilter、AdviceFilter、PathMatchingFilter、AccessControlFilter、AuthenticationFilter、AuthorizationFilter等构成shiro的拦截器基本架构。
(-- 关于各shiro各个拦截器的简单介绍可参考博文:Apache Shiro学习笔记(六)Shiro Filter介绍)
二、初始化过程
启动项目时spring首先会通过doGetObjectFromFactoryBean()方法来初始化Shiro的拦截器入口工厂类,即org.apache.shiro.spring.web.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;
}
ShiroFilterFactoryBean提供的获取实例的工厂方法:
/**
* 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