Apache Shiro源码解读之Subject的创建
Subject是Shiro中十分重要的对象,可以简单的理解为"当前用户"。 首先来看下Subject的继承关系
不论是web应用程序还是普通应用程序,我们在某个方法里面都已通过以下方法来获取Subject对象并使用Session
Subject currentUser = org.apache.shiro.SecurityUtils.getSubject();if ( !currentUser.isAuthenticated() ) { UsernamePasswordToken token = new UsernamePasswordToken("user", "password"); token.setRememberMe(true); try { currentUser.login( token ); Session session = currentUser.getSession(); session.setAttribute( "key", "value" ); } catch ( UnknownAccountException uae ) { //用户不存在 } catch ( IncorrectCredentialsException ice ) { //密码错误 } catch ( LockedAccountException lae ) { //用户被锁 } catch ( AuthenticationException ae ) { //unexpected condition - error? }}
该SecurityUtils位于shiro-core.jar中
简单的两句代码就可以完成登录验证,也可以使用Session,看起来十分简单,可简单的背后可能又隐藏着许多复杂之处,接下来我们就来一探究竟。
/** * Accesses the currently accessible {@code Subject} for the calling code depending on runtime environment. * * @since 0.2 */public abstract class SecurityUtils { public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }}
Subject首先直接从TreadContext里面直接获取,如果没有获取到则使用Subject的内部内来创建,然后再绑定到ThreadContext上。那么我们接着看看ThreadContext的定义
public abstract class ThreadContext { public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY"; private static final ThreadLocal
ThreadContext里面维持着一个LocalThread对象,可见Subject是与当前线程相绑定的。
public interface Subject { public static class Builder { private final SubjectContext subjectContext; private final SecurityManager securityManager; public Builder() { this(SecurityUtils.getSecurityManager()); } public Builder(SecurityManager securityManager) { if (securityManager == null) { throw new NullPointerException("SecurityManager method argument cannot be null."); } this.securityManager = securityManager; this.subjectContext = newSubjectContextInstance(); if (this.subjectContext == null) { throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' " + "cannot be null."); } this.subjectContext.setSecurityManager(securityManager); } protected SubjectContext newSubjectContextInstance() { return new DefaultSubjectContext(); } public Subject buildSubject() { return this.securityManager.createSubject(this.subjectContext); } }}
至此,Builder创建Subject的时候是委托给SecurityManager来创建的,而SecurityManager又是从SecurityUtils从返回。那么还得追溯下SecurityManager是如何被创建的才能进一步得知Subject的创建。而根据Subject的继承关系图可知,它本身只是个接口,那么其实现类又该对应的哪个,如何判定应该使用哪个?
对于在web环境中集成shrio时,一般是在web.xml文件中添加以下的配置
org.apache.shiro.web.env.EnvironmentLoaderListener ShiroFilter org.apache.shiro.web.servlet.ShiroFilter ShiroFilter /* REQUEST FORWARD INCLUDE ERROR
既然是在请求的过程中获取并使用的Subject, 那我们就来看看ShiroFilter类都包含了哪些内容,首先看看ShiroFilter的继承关系
public class ShiroFilter extends AbstractShiroFilter { @Override public void init() throws Exception { WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext()); setSecurityManager(env.getWebSecurityManager()); FilterChainResolver resolver = env.getFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } }}
在这里之看到了init方法,看名字应该是初始化给Filter时候运行的,那么在何处调用的,我们继续看看他的父类
public abstract class AbstractFilter extends ServletContextSupport implements Filter { public final void init(FilterConfig filterConfig) throws ServletException { setFilterConfig(filterConfig); try { onFilterConfigSet(); } catch (Exception e) { if (e instanceof ServletException) { throw (ServletException) e; } else { if (log.isErrorEnabled()) { log.error("Unable to start Filter: [" + e.getMessage() + "].", e); } throw new ServletException(e); } } }}
public abstract class AbstractShiroFilter extends OncePerRequestFilter { protected final void onFilterConfigSet() throws Exception { //added in 1.2 for SHIRO-287: applyStaticSecurityManagerEnabledConfig(); init(); //调用了子类【ShiroFilter】的init()方法(开始得到WebEnvironment等对象) ensureSecurityManager(); //确认SecurityManager是否存在,不存在则创建默认的DefaultWebSecurityManager对象 //added in 1.2 for SHIRO-287: if (isStaticSecurityManagerEnabled()) { /* 注意:这里很重要,在WEB环境是不建议将SecurityManager对象保存在静态变量中的。。。 根据Filter配置的初始化参数判断是否要将SecurityManager通过SecurityUtils当做静态变量进行保存 */ SecurityUtils.setSecurityManager(getSecurityManager()); } }}
可知AbstractFilter调用了AbstractShiroFilter,然后再调用了ShiroFilter的init方法。 init方法的目的就是为了获得WebEnvironment对象,其WebUtils里的代码就简单了,就是从ServletContext中直接获取WebEnvironment对象,如果为空,则会抛出异常。
public class WebUtils { public static WebEnvironment getRequiredWebEnvironment(ServletContext sc) throws IllegalStateException { WebEnvironment we = getWebEnvironment(sc); if (we == null) { throw new IllegalStateException("No WebEnvironment found: no EnvironmentLoaderListener registered?"); } return we; } public static WebEnvironment getWebEnvironment(ServletContext sc) { return getWebEnvironment(sc, EnvironmentLoader.ENVIRONMENT_ATTRIBUTE_KEY); } public static WebEnvironment getWebEnvironment(ServletContext sc, String attrName) { if (sc == null) { throw new IllegalArgumentException("ServletContext argument must not be null."); } Object attr = sc.getAttribute(attrName); if (attr == null) { return null; } if (attr instanceof RuntimeException) { throw (RuntimeException) attr; } if (attr instanceof Error) { throw (Error) attr; } if (attr instanceof Exception) { throw new IllegalStateException((Exception) attr); } if (!(attr instanceof WebEnvironment)) { throw new IllegalStateException("Context attribute is not of type WebEnvironment: " + attr); } return (WebEnvironment) attr; }}
接着我们看下WebEnvironment的定义:
public interface Environment { /** * Returns the application's {@code SecurityManager} instance. * * @return the application's {@code SecurityManager} instance. */ SecurityManager getSecurityManager();}public interface WebEnvironment extends Environment { /** * Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one * is not available. * * @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one * is not available. */ FilterChainResolver getFilterChainResolver(); /** * Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance. A web application * typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}. * * @return the {@code ServletContext} associated with this {@code WebEnvironment} instance. */ ServletContext getServletContext(); /** * Returns the web application's security manager instance. * * @return the web application's security manager instance. */ WebSecurityManager getWebSecurityManager();}
在WebEnvironment里面直接保存了全局唯一的SecurityManager对象。接下来我们需要追踪SecurityManager对象的创建过程。我们就得回到 到以下对象上
org.apache.shiro.web.env.EnvironmentLoaderListener
public class EnvironmentLoaderListener extends EnvironmentLoader implements ServletContextListener { /** * Initializes the Shiro {@code WebEnvironment} and binds it to the {@code ServletContext} at application * startup for future reference. * * @param sce the ServletContextEvent triggered upon application startup */ public void contextInitialized(ServletContextEvent sce) { initEnvironment(sce.getServletContext()); } /** * Destroys any previously created/bound {@code WebEnvironment} instance created by * the {@link #contextInitialized(javax.servlet.ServletContextEvent)} method. * * @param sce the ServletContextEvent triggered upon application shutdown */ public void contextDestroyed(ServletContextEvent sce) { destroyEnvironment(sce.getServletContext()); }}public class EnvironmentLoader { public static final String ENVIRONMENT_ATTRIBUTE_KEY = EnvironmentLoader.class.getName() + ".ENVIRONMENT_ATTRIBUTE_KEY"; public WebEnvironment initEnvironment(ServletContext servletContext) throws IllegalStateException { if (servletContext.getAttribute(ENVIRONMENT_ATTRIBUTE_KEY) != null) { String msg = "There is already a Shiro environment associated with the current ServletContext. " + "Check if you have multiple EnvironmentLoader* definitions in your web.xml!"; throw new IllegalStateException(msg); } servletContext.log("Initializing Shiro environment"); log.info("Starting Shiro environment initialization."); long startTime = System.currentTimeMillis(); try { WebEnvironment environment = createEnvironment(servletContext); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY,environment); log.debug("Published WebEnvironment as ServletContext attribute with name [{}]", ENVIRONMENT_ATTRIBUTE_KEY); if (log.isInfoEnabled()) { long elapsed = System.currentTimeMillis() - startTime; log.info("Shiro environment initialized in {} ms.", elapsed); } return environment; } catch (RuntimeException ex) { log.error("Shiro environment initialization failed", ex); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, ex); throw ex; } catch (Error err) { log.error("Shiro environment initialization failed", err); servletContext.setAttribute(ENVIRONMENT_ATTRIBUTE_KEY, err); throw err; } } protected WebEnvironment createEnvironment(ServletContext sc) { WebEnvironment webEnvironment = determineWebEnvironment(sc); if (!MutableWebEnvironment.class.isInstance(webEnvironment)) { throw new ConfigurationException("Custom WebEnvironment class [" + webEnvironment.getClass().getName() + "] is not of required type [" + MutableWebEnvironment.class.getName() + "]"); } String configLocations = sc.getInitParameter(CONFIG_LOCATIONS_PARAM); boolean configSpecified = StringUtils.hasText(configLocations); if (configSpecified && !(ResourceConfigurable.class.isInstance(webEnvironment))) { String msg = "WebEnvironment class [" + webEnvironment.getClass().getName() + "] does not implement the " + ResourceConfigurable.class.getName() + "interface. This is required to accept any " + "configured " + CONFIG_LOCATIONS_PARAM + "value(s)."; throw new ConfigurationException(msg); } MutableWebEnvironment environment = (MutableWebEnvironment) webEnvironment; environment.setServletContext(sc); if (configSpecified && (environment instanceof ResourceConfigurable)) { ((ResourceConfigurable) environment).setConfigLocations(configLocations); } customizeEnvironment(environment); LifecycleUtils.init(environment); //注意:这了会调用environment的init方法来初始化environment return environment; } protected WebEnvironment determineWebEnvironment(ServletContext servletContext) { Class extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); WebEnvironment webEnvironment = null; // try service loader next if (webEnvironmentClass == null) { webEnvironment = webEnvironmentFromServiceLoader(); } // if webEnvironment is not set, and ENVIRONMENT_CLASS_PARAM prop was not set, use the default if (webEnvironmentClass == null && webEnvironment == null) { webEnvironmentClass = getDefaultWebEnvironmentClass(); } // at this point, we anything is set for the webEnvironmentClass, load it. if (webEnvironmentClass != null) { webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass); } return webEnvironment; } protected Class extends WebEnvironment> getDefaultWebEnvironmentClass() { return IniWebEnvironment.class; }}
经过一路的奔波,最终创建了默认的Environment对象IniWebEnvironment。
接着我们再看看IniWebEnvironment对象初始化都做了些啥事
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable { public static final String DEFAULT_WEB_INI_RESOURCE_PATH = "/WEB-INF/shiro.ini"; public static final String FILTER_CHAIN_RESOLVER_NAME = "filterChainResolver"; private static final Logger log = LoggerFactory.getLogger(IniWebEnvironment.class); /** * The Ini that configures this WebEnvironment instance. */ private Ini ini; private WebIniSecurityManagerFactory factory; public IniWebEnvironment() { factory = new WebIniSecurityManagerFactory(); } public void init() { //解析指定或默认位置的配置文件并生成对应的Ini对象 setIni(parseConfig()); configure(); } protected void configure() { this.objects.clear(); WebSecurityManager securityManager = createWebSecurityManager(); setWebSecurityManager(securityManager); FilterChainResolver resolver = createFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } } /* 将Ini对象传递给WebIniSecurityManagerFactory,并构建SecurityManager对象 */ protected WebSecurityManager createWebSecurityManager() { Ini ini = getIni(); if (!CollectionUtils.isEmpty(ini)) { factory.setIni(ini); } Map defaults = getDefaults(); if (!CollectionUtils.isEmpty(defaults)) { factory.setDefaults(defaults); } WebSecurityManager wsm = (WebSecurityManager)factory.getInstance(); //SHIRO-306 - get beans after they've been created (the call was before the factory.getInstance() call, //which always returned null. Map beans = factory.getBeans(); if (!CollectionUtils.isEmpty(beans)) { this.objects.putAll(beans); } return wsm; } protected Map getDefaults() { Map defaults = new HashMap(); defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory()); return defaults; }}
接着继续跟踪WebIniSecurityManagerFactory的执行
public class WebIniSecurityManagerFactory extends IniSecurityManagerFactory { protected SecurityManager createDefaultInstance() { return new DefaultWebSecurityManager(); }}
附SecurityManager继承关系,后面再详细解析SecurityManager
HTTP请求处理过程
1,每个http请求都被ShoriFilter拦截进行处理
2,将SecurityManager对象和包装后的Request和Response作为构造参数创建WebSubject.Builder实例,并调用buildWebSubject方法创建Subject
3,在构造方法中创新新的SubjectContext实例,并将SecurityManager保存到SubjectContext实例中
4,将Request和Response也添加到SubjectContext中保存
5,将subjectContext作为参数,调用SecurityManager的createSubject方法创建Subject对象
6,将SubjectContext作为参数,调用SubjectFactory【DefaultSubjectFactory】的createSubject方法创建Subject
7,接着取出SubjectContext一路收集来的数据来构建DelegatingSubject对象并返回。
8,当调用Subject的getSession方法的时候,如果Session不存在,则首先创建一个新的DefaultSessionContext实例并设置host值【可能是空】
9,将sessionContext对象作为参数调用securityManager的start方法来创建Session
10,从SessionContext中取出HttpServletRequest,并调用HttpServletRequest的getSession方法来获取HttpSession,同时从SessionContext中取出host,使用这两个值作为构造函数的参数实例化HttpServletSession类。
11,到此,Session的创建过程结束,此时的HttpServletSession纯粹只是HttpSession的代理一样。