Apache Shiro源码解读之SecurityManager的创建
对于Shiro(v1.2+)的SecurityManager的创建,在普通的应用程序中一般可以在main方法中这么创建
Factory factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();
该方法读取classpath路径下的shiro.ini文件来构建SecurityManager,然而在web应用程序中,其是怎么创建的我们接下来逐步分析。
在web环境中我们会使用以下的Listener,而SecurityManager的创建就在Listener的初始化过程中【该Listener在shrio-web.jar中】
org.apache.shiro.web.env.EnvironmentLoaderListener
EnvironmentLoaderListener的继承关系很简单,如下所示
EnvironmentLoader的作用是负责在应用程序启动的时候负责加载Shiro,同时将org.apache.shiro.web.mgt.WebSecurityManager设置到ServletContext中。
在初始化Shiro的过程中,在web.xml文件中配置的上下文参数"shiroEnvironmentClass"和"shiroConfigLocations"可以指导Shiro的初始化过程,当然,这两个参数不是必须配置的,有默认值。
shiroEnvironmentClass:制定继承自WebEnvironment的自定义类,默认对象为IniWebEnvironment。
shiroConfigLocations:制定shiro初始化时用的配置文件路径,默认会先查询/WEB-INF/shiro.ini,如果没找到再查找classpath:shiro.ini。
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 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 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; //保存当前的ServletContext对象 environment.setServletContext(sc); //如果在web.xml设置了配置文件的路径,则在此设置到environment中 if (configSpecified && (environment instanceof ResourceConfigurable)) { ((ResourceConfigurable) environment).setConfigLocations(configLocations); } //构造方法,默认未实现 customizeEnvironment(environment); //调用environment的init方法初始化environment对象 LifecycleUtils.init(environment); return environment; } protected WebEnvironment determineWebEnvironment(ServletContext servletContext) { //从ServletContext的参数中获取WebEnvironment的配置--shiroEnvironmentClass,如果有则创建实例返回 Class extends WebEnvironment> webEnvironmentClass = webEnvironmentClassFromServletContext(servletContext); WebEnvironment webEnvironment = null; // 尝试通过Java的ServiceLoader来查找WebEnvironment的实现类 if (webEnvironmentClass == null) { webEnvironment = webEnvironmentFromServiceLoader(); } // 如果上面的步骤都没找到,则使用默认的WebEnvironment实现类IniWebEnvironment if (webEnvironmentClass == null && webEnvironment == null) { webEnvironmentClass = getDefaultWebEnvironmentClass(); } // 创建WebEnvironment的实例 if (webEnvironmentClass != null) { webEnvironment = (WebEnvironment) ClassUtils.newInstance(webEnvironmentClass); } return webEnvironment; } private WebEnvironment webEnvironmentFromServiceLoader() { WebEnvironment webEnvironment = null; /* * 使用Java的ServiceLoader方式来查找WebEnvironment的实现类(查找jar包中META-INF下的services文件夹中的文件); * 例如在某个services文件夹中有个名为org.apache.shiro.web.env.WebEnvironment的文件,然后在文件里面保存WebEnvironment的实现类全路径; * 可见,文件名为接口的全路径,里面的内容为接口的实现类 * */ ServiceLoader serviceLoader = ServiceLoader.load(WebEnvironment.class); Iterator iterator = serviceLoader.iterator(); // 如果找到则使用第一个 if (iterator.hasNext()) { webEnvironment = iterator.next(); } // 如果不止找到一个,则抛出异常 if (iterator.hasNext()) { List allWebEnvironments = new ArrayList(); allWebEnvironments.add(webEnvironment.getClass().getName()); while (iterator.hasNext()) { allWebEnvironments.add(iterator.next().getClass().getName()); } throw new ConfigurationException("ServiceLoader for class [" + WebEnvironment.class + "] returned more then one " + "result. ServiceLoader must return zero or exactly one result for this class. Select one using the " + "servlet init parameter '"+ ENVIRONMENT_CLASS_PARAM +"'. Found: " + allWebEnvironments); } return webEnvironment; }}
综上得知,查找WebEnvironment的实现类经历了三次查找
1)从ServletContext的初始化参数
2)从jar包查找实现类
3)使用默认的IniWebEnvironment
在得到WebEnvironment的实现类并创建好实例后,接着便会调用其init方法,这里假设得到的是默认的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() { //实例化WebIniSecurityManagerFactory对象 factory = new WebIniSecurityManagerFactory(); } /** * 初始化本实例 */ public void init() { //解析shiiro.ini配置文件并生成对应的Ini实例 setIni(parseConfig()); //使用Ini信息,通过WebIniSecurityManagerFactory创建WebSecurityManager实例 configure(); }}protected Ini parseConfig() { //直接取,首次运行肯定为null Ini ini = getIni(); //获取配置文件路径【该路径信息就是web.xml文件中配置的,实例化该类的时候已经在EnvironmentLoader中设置】 String[] configLocations = getConfigLocations(); if (log.isWarnEnabled() && !CollectionUtils.isEmpty(ini) && configLocations != null && configLocations.length > 0) { //如果Ini对象不为空,并且configLocations也不为空,给出提示信息 log.warn("Explicit INI instance has been provided, but configuration locations have also been " + "specified. The {} implementation does not currently support multiple Ini config, but this may " + "be supported in the future. Only the INI instance will be used for configuration.", IniWebEnvironment.class.getName()); } if (CollectionUtils.isEmpty(ini)) { log.debug("Checking any specified config locations."); //从指定路径下的配置文件中创建Ini实例 ini = getSpecifiedIni(configLocations); } if (CollectionUtils.isEmpty(ini)) { log.debug("No INI instance or config locations specified. Trying default config locations."); /* * 如果没有在web.xml中配置,则从默认的路径下读取配置文件并创建实例 * 1,/WEB-INF/shiro.ini * 2,classpath:shiro.ini * */ ini = getDefaultIni(); } /* * 为了保持向后兼容而提供getFrameworkIni方法来创建Ini对象并与上面得到的Ini对象合并. * getFrameworkIni的默认实现返回null,经过合并处理后返回的还是上面的Ini对象 * */ ini = mergeIni(getFrameworkIni(), ini); if (CollectionUtils.isEmpty(ini)) { String msg = "Shiro INI configuration was either not found or discovered to be empty/unconfigured."; throw new ConfigurationException(msg); } return ini; } /** * 解析配置文件创建Ini实例对象 * */ protected Ini createIni(String configLocation, boolean required) throws ConfigurationException { Ini ini = null; if (configLocation != null) { ini = convertPathToIni(configLocation, required); } if (required && CollectionUtils.isEmpty(ini)) { String msg = "Required configuration location '" + configLocation + "' does not exist or did not " + "contain any INI configuration."; throw new ConfigurationException(msg); } return ini; } /** * 加载制定路径的配置文件,然后将文件流作为参数调用Ini实例对象的load方法来初始化Ini对象 * */ private Ini convertPathToIni(String path, boolean required) { Ini ini = null; if (StringUtils.hasText(path)) { InputStream is = null; //SHIRO-178: Check for servlet context resource and not only resource paths: if (!ResourceUtils.hasResourcePrefix(path)) { is = getServletContextResourceStream(path); } else { try { is = ResourceUtils.getInputStreamForPath(path); } catch (IOException e) { if (required) { throw new ConfigurationException(e); } else { if (log.isDebugEnabled()) { log.debug("Unable to load optional path '" + path + "'.", e); } } } } if (is != null) { ini = new Ini(); ini.load(is); } else { if (required) { throw new ConfigurationException("Unable to load resource path '" + path + "'"); } } } return ini; }
再看看Ini对象的初始化过程
public class Ini implements Map { private static transient final Logger log = LoggerFactory.getLogger(Ini.class); public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section public static final String DEFAULT_CHARSET_NAME = "UTF-8"; public static final String COMMENT_POUND = "#"; public static final String COMMENT_SEMICOLON = ";"; public static final String SECTION_PREFIX = "["; public static final String SECTION_SUFFIX = "]"; protected static final char ESCAPE_TOKEN = '\\'; private final Map sections; /** * Creates a new empty {@code Ini} instance. */ public Ini() { this.sections = new LinkedHashMap(); } public void load(InputStream is) throws ConfigurationException { if (is == null) { throw new NullPointerException("InputStream argument cannot be null."); } InputStreamReader isr; try { isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME); } catch (UnsupportedEncodingException e) { throw new ConfigurationException(e); } load(isr); } public void load(Reader reader) { Scanner scanner = new Scanner(reader); try { load(scanner); } finally { try { scanner.close(); } catch (Exception e) { log.debug("Unable to cleanly close the InputStream scanner. Non-critical - ignoring.", e); } } } public void load(Scanner scanner) { String sectionName = DEFAULT_SECTION_NAME; StringBuilder sectionContent = new StringBuilder(); //循环读取每一行 while (scanner.hasNextLine()) { String rawLine = scanner.nextLine(); //取出两边的空格 String line = StringUtils.clean(rawLine); if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) { //忽略空行和注释 continue; } //获取section名称,格式为 [main] 这种,此时返回"main" String newSectionName = getSectionName(line); if (newSectionName != null) { //前面section的配置信息收集完成,添加section配置 addSection(sectionName, sectionContent); //为本次的section重置StringBuilder对象,用户存放该section的配置信息 sectionContent = new StringBuilder(); sectionName = newSectionName; if (log.isDebugEnabled()) { log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX); } } else { //添加配置信息 sectionContent.append(rawLine).append("\n"); } } //添加Section的配置信息 addSection(sectionName, sectionContent); } private void addSection(String name, StringBuilder content) { if (content.length() > 0) { String contentString = content.toString(); String cleaned = StringUtils.clean(contentString); if (cleaned != null) { //构建Section对象【静态内部类】 Section section = new Section(name, contentString); if (!section.isEmpty()) { //以键值对的方式保存Section对象 sections.put(name, section); } } } } public static class Section implements Map { private final String name; private final Map props; /* * 解析收集的配置信息,将配置信息保存到props对象中 * */ private Section(String name, String sectionContent) { if (name == null) { throw new NullPointerException("name"); } this.name = name; Map props; if (StringUtils.hasText(sectionContent) ) { props = toMapProps(sectionContent); } else { props = new LinkedHashMap(); } if ( props != null ) { this.props = props; } else { this.props = new LinkedHashMap(); } } }}
到此,在IniWebEnvironment实例中通过解析配置文件得到了Ini对象,该对象里面保存了配置文件中的每个Section信息,那么接着就要使用该Ini对象来构建WebSecurityManager了,也就是调用IniWebEnvironment 的configure方法
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable { protected void configure() { //Map对象 this.objects.clear(); WebSecurityManager securityManager = createWebSecurityManager(); setWebSecurityManager(securityManager); FilterChainResolver resolver = createFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } } protected Map getDefaults() { Map defaults = new HashMap(); defaults.put(FILTER_CHAIN_RESOLVER_NAME, new IniFilterChainResolverFactory()); return defaults; } protected WebSecurityManager createWebSecurityManager() { //已经创建好的Ini对象 Ini ini = getIni(); if (!CollectionUtils.isEmpty(ini)) { factory.setIni(ini); } Map defaults = getDefaults(); if (!CollectionUtils.isEmpty(defaults)) { factory.setDefaults(defaults); } //从WebIniSecurityManagerFactory实例中创建WebSecurityManager 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; }}
接着看看WebIniSecurityManagerFactory的getInstance方法的实现
由图可见,在调用getInstance方法的时候,其实执行的是位于AbstractFactory中的getInstance方法
public abstract class AbstractFactory implements Factory { public T getInstance() { T instance; if (isSingleton()) { if (this.singletonInstance == null) { this.singletonInstance = createInstance(); } instance = this.singletonInstance; } else { instance = createInstance(); } if (instance == null) { String msg = "Factory 'createInstance' implementation returned a null object."; throw new IllegalStateException(msg); } return instance; } /* * 子类(IniFactorySupport)实现创建实例的过程 * */ protected abstract T createInstance();}
public abstract class IniFactorySupport extends AbstractFactory { public T createInstance() { /* * 获取Ini对象,前面已经设置进来。 * 如果ini对象不存在,还会从默认的路径来创建Ini对象 * */ Ini ini = resolveIni(); T instance; if (CollectionUtils.isEmpty(ini)) { //如果Ini对象不存在,则调动子类(IniSecurityManagerFactory)使用默认的SecurityManager实例对象 log.debug("No populated Ini available. Creating a default instance."); instance = createDefaultInstance(); if (instance == null) { String msg = getClass().getName() + " implementation did not return a default instance in " + "the event of a null/empty Ini configuration. This is required to support the " + "Factory interface. Please check your implementation."; throw new IllegalStateException(msg); } } else { log.debug("Creating instance from Ini [" + ini + "]"); //调用子类(IniSecurityManagerFactory),根据Ini对象的信息来构建SecurityManager对象 instance = createInstance(ini); if (instance == null) { String msg = getClass().getName() + " implementation did not return a constructed instance from " + "the createInstance(Ini) method implementation."; throw new IllegalStateException(msg); } } return instance; } protected abstract T createInstance(Ini ini); protected abstract T createDefaultInstance();}
public class IniSecurityManagerFactory extends IniFactorySupport { public static final String MAIN_SECTION_NAME = "main"; public static final String SECURITY_MANAGER_NAME = "securityManager"; public static final String INI_REALM_NAME = "iniRealm"; private ReflectionBuilder builder; public IniSecurityManagerFactory() { this.builder = new ReflectionBuilder(); } //默认的SecurityManager对象【其实被WebIniSecurityManagerFactory复写,返回的是DefaultWebSecurityManager】 protected SecurityManager createDefaultInstance() { return new DefaultSecurityManager(); } //根据Ini来创建SecurityManager对象 protected SecurityManager createInstance(Ini ini) { if (CollectionUtils.isEmpty(ini)) { throw new NullPointerException("Ini argument cannot be null or empty."); } SecurityManager securityManager = createSecurityManager(ini); if (securityManager == null) { String msg = SecurityManager.class + " instance cannot be null."; throw new ConfigurationException(msg); } return securityManager; } private SecurityManager createSecurityManager(Ini ini) { return createSecurityManager(ini, getConfigSection(ini)); } //获取[main]的配置,如果没得到则获取默认的配置 private Ini.Section getConfigSection(Ini ini) { Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME); if (CollectionUtils.isEmpty(mainSection)) { //try the default: mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME); } return mainSection; } @SuppressWarnings({"unchecked"}) private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) { /* * 注意,createDefaults被子类WebIniSecurityManagerFactory复写, * 但其实也会首先调用本类的createDefaults方法,只是在结果中再添加了些默认的Filter实例。 * * 然后将结果保存在ReflectionBuilder对象的objects【Map】属性中,此时里面包含了默认的SecurityManager、Realm以及各种默认Filter实例; * * 最后将createDefaults返回的Map全部加到ReflectionBuilder对象的objects【Map】中取缓存 * */ getReflectionBuilder().setObjects(createDefaults(ini, mainSection)); //使用ReflectionBuilder构建对象【创建实例对象,加入到objects变量中,然后执行各个对象的init方法,同时返回objects对象】 Map objects = buildInstances(mainSection); //直接从ReflectionBuilder对象中取出SecurityManager类型的对象 SecurityManager securityManager = getSecurityManagerBean(); /* * 如果securityManager不为RealmSecurityManager类型则返回true; * 如果是RealmSecurityManager类型,但是里面没有Realm实例,返回为true; * 否则返回false * */ boolean autoApplyRealms = isAutoApplyRealms(securityManager); if (autoApplyRealms) { //筛选其中的Realms对象【Realm或RealmFactory类型】 Collection realms = getRealms(objects); if (!CollectionUtils.isEmpty(realms)) { //如果securityManager不是RealmSecurityManager类型则抛出异常,否则给securityManager设置Realms applyRealmsToSecurityManager(realms, securityManager); } } return securityManager; }}
到此,SecurityManager实例创建完成,并设置到IniWebEnvironment的属性objects[Map]中
public class IniWebEnvironment extends ResourceBasedWebEnvironment implements Initializable, Destroyable { protected void configure() { //Map对象 this.objects.clear(); WebSecurityManager securityManager = createWebSecurityManager(); setWebSecurityManager(securityManager); //获取shiro.ini文件中配置的'filters' 或 'urls'项的Filter,加入objects对象中 FilterChainResolver resolver = createFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } }}
到此,Shiro的初始化过程完成,在EnvironmentLoaderListener 中将会把该IniWebEnvironment对象保存在ServletContext下供后面使用。
大致流程总结
系统启动的时候执行EnvironmentLoaderListener初始化方法并创建WebEnvironment实例,同时将实例对象保存到ServletContext中
1,创建WebEnvironment对象
1)读取web.xml中的上下文参数shiroEnvironmentClass
2)通过ServiceLoader方式查找jar包中的配置
3)是用默认的IniWebEnvironment类型
2,调用WebEnvironment的init方法初始化WebEnvironment实例
注:WebEnvironment构造犯法里面会创建WebIniSecurityManagerFactory实例factory。
1)从指定或默认的路径下解析shiro.ini文件生成Ini实例
2)将Ini实例设置给factory的ini属性
3)将默认的IniFilterChainResolverFactory设置给factory的defaultBeans(Map)属性
4)调用factory的getInstance方法创建SecurityManager对象
--解析Ini对象里面的信息,创建Realm等对象并设置给SecurityManager实例
5)将SecurityManager返回的objects(Map)添加到WebEnvironment的objects中。
默认的SecurityManager: DefaultWebSecurityManager
后面讲接着介绍Session和Realm的使用