千家信息网

Apache Shiro源码解读之SecurityManager的创建

发表于:2025-02-07 作者:千家信息网编辑
千家信息网最后更新 2025年02月07日,对于Shiro(v1.2+)的SecurityManager的创建,在普通的应用程序中一般可以在main方法中这么创建Factory factory = new IniSecurityManagerF
千家信息网最后更新 2025年02月07日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 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的使用

0