SHIRO源码解读——SecurityManager创建

作者: PioneerYi | 来源:发表于2019-07-26 22:55 被阅读8次

    一、初始化一个SecurityManager

    最简单的初始化SecurityManager的方式如下:

    DefaultSecurityManager securityManager= new DefaultSecurityManager();   
    

    但是,我们一般使用方式都是自定义一些配置,然后根据配置文件初始化SecurityManager,如下:

    //解析ini文件为Ini对象
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-config.ini");  
    //根据Ini对象初始化SecurityManager对象  
    SecurityManager securityManager = factory.getInstance();  
    

    那么Shiro是如何根据配置文件初始化一个SecurityManager的了?

    根据配置文件初始化SecurityManager的主要分为两部分:

    • 解析Ini配置文件;
    • 根据配置文件初始化一个SecurityManeger实例

    二、解析Ini配置文件

    IniSecurityManagerFactory类构造函数,传入Ini配置文件路径,然后将ini文件解析工作交给Ini的静态方法fromResourcePath完成。

    public IniSecurityManagerFactory(String iniResourcePath) {
        this(Ini.fromResourcePath(iniResourcePath)); 
    } 
    

    Ini解析完配置后,将结果返回,IniSecurityManagerFactory将解析后的Ini对象设置为自身持有。

    public IniSecurityManagerFactory(Ini config) {  
        setIni(config);  
    }  
    

    这里有必要解析下Ini是什么?Ini是Shiro的配置数据结构类,其内部有一个Map类型的成员变量保存所有配置Section,其定义如下:

    public class Ini implements Map<String, Ini.Section> {
        private final Map<String, Section> sections;
        ......
    }
    

    那么Section又是什么了?Shiro的配置文件的每一个块即为一个Section,源码对于Section的解释如下:

    An {@code Ini.Section} is String-key-to-String-value Map, identifiable by a {@link #getName() name} unique within an {@link Ini} instance.
    

    其定义如下:

    public static class Section implements Map<String, String> {
        private final String name;
        private final Map<String, String> props;
        ......
    }
    

    下面详细看看Ini类是如何对配置文件进行解析的,主要分为两步:
    1)获取文件流;
    2)获取到文件流后,对其进行解析;

    获取文件流过程比较简单,这里不做分析,主要看看如何进行解析的,执行过程代码片段:

    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);  
            //此处跳过ini文件格式的注释及空值  
            if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {  
                //skip empty lines and comments:  
                continue;  
            }  
            //此处主要获取section部分,根据[]规则  
            String newSectionName = getSectionName(line);  
            if (newSectionName != null) {  
                //此处代码主要用于构造Section对象,并放进sections集合中  
                addSection(sectionName, sectionContent);  
                sectionContent = new StringBuilder();    
                sectionName = newSectionName;   
                if (log.isDebugEnabled()) {  
                    log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);  
                }  
            } else {  
                //normal line - add it to the existing content buffer:          sectionContent.append(rawLine).append("\n"); 
            }  
        }  
        //finish any remaining buffered content: 
        addSection(sectionName, sectionContent); 
    }  
    
    

    上段代码主要是组装ini文件中的Section。Section、Ini类都是实现了Map接口。Section持有的LinkedHashMap容器实际上是当前section中的所有键值对,而Ini持有的LinkedHashMap容器实际上是所有Section名称与section对象的键值对。

    上面代码逻辑为:逐行读取配置文件,遇到行内容为"["开头,"]"结尾,那么就是一个新的Section,接下来的内容为此Section的内容(按行分割),直到遇到下一个Section。

    Section中将每行配置按照":"或"="分割成一个个key和value对最终形成 Map<String,String> props。

    至此,Ini文件的解析已经完成,其配置文件中的内容已全部以map的形式存放在Ini实例中。

    不过将配置文件解析为MAP,只是完成了第一步。使用过shiro的人都知道,shiro的配置项并不是简单的基本类型,其配置项key可能是一个类对象,配置项value也可能是一个类对象,那么shiro是如何处理这些类对象的了?

    在初始化SecurityManager时会对这些对象进行解析,后续会讲到。之所以想总结本篇文章,其中很重要的一个点就是觉得shiro支持配置文件配置类结构对象,觉得这种方式很棒,所以探究了下。

    三、根据配置文件初始化SecurtiyManager

    1、SecurityManager初始化时序图和类图

    SecurityManager初始化时序图如下:


    SecurityManager初始化时序图

    SecurityManager初始化所涉及的类的类图如下:


    SecurityManager初始化类图

    SecurityManager的主要初始化工作在InisecurityManagerFactory中完成,InisecurityManagerFactory利用了一些其他类或者工具来辅助完成初始化工作。比如利用ClassUtils类通过类名得到类实例,利用BeanUtil显式设置对象属性。

    接下来看看初始化过程详细分析。

    2、SecurityManager初始化源码详细分析

    IniSecurityManagerFactory

    Factory,AbstractFactory,IniFactorySupport均是泛型类,层层继承,IniSecurityManagerFactory是一个继承IniFactorySupport的实例类,实例类型为SecurityManager,类的主要工作就是根据Ini配置初始化SecurityManager。

    重点关注一下这个方法:

    private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) {
      getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
        Map<String, ?> objects = buildInstances(mainSection);
        SecurityManager securityManager = getSecurityManagerBean();
        boolean autoApplyRealms = isAutoApplyRealms(securityManager);
        if (autoApplyRealms) {
            //realms and realm factory might have been created - pull them out first so we can
            //initialize the securityManager:
            Collection<Realm> realms = getRealms(objects);
            //set them on the SecurityManager
            if (!CollectionUtils.isEmpty(realms)) {
                applyRealmsToSecurityManager(realms, securityManager);
            }
        }
        return securityManager;
    }
    

    第一步:

    getReflectionBuilder().setObjects(createDefaults(ini, mainSection));
    

    此方法首先根据配置文件创建一个默认的SecurityManager,然后将此默认的SecurityManager的Bean存入ReflectionBuilder的Objects Map中,同时根据配置文件中是否含有roles或者users配置决定是否显式创建Realm,创建Realm后也存入ReflectionBuilder的Objects Map中。

    默认SecurityManager构造时会进行如下初始化:

    public DefaultSecurityManager() {
        setEventBus(new DefaultEventBus());
        this.authenticator = new ModularRealmAuthenticator();
        this.authorizer = new ModularRealmAuthorizer();
        this.sessionManager = new DefaultSessionManager();
        this.subjectFactory = new DefaultSubjectFactory();
        this.subjectDAO = new DefaultSubjectDAO();
    }
    

    第二步:

    Map<String, ?> objects = buildInstances(mainSection);
    

    根据main section配置intance一个新的securityManager实例,这一步比较复杂,主要是ReflectionBuilder类的工作,后面会单独讲一下ReflectionBuilder类。

    第三步:
    上面一二两步余下代码为第三步内容,第三步主要是判断SecurityManager是否包含Realm,如果包含Realm,就将Realm赋给SecurityManger。

    ReflectionBudiler

    ReflectionBudiler类的主要工作就是根据配置文件的配置得到各个对象的Bean,对象包括SecurityManager本身,以及其成员,包括Authorizer,Realm等。

    public Map<String, ?> buildObjects(Map<String, String> kvPairs) {
        if (kvPairs != null && !kvPairs.isEmpty()) {
            BeanConfigurationProcessor processor = new BeanConfigurationProcessor();
            for (Map.Entry<String, String> entry : kvPairs.entrySet()) {
                String lhs = entry.getKey();
                String rhs = interpolator.interpolate(entry.getValue());
                String beanId = parseBeanId(lhs);
                if (beanId != null) { 
                    processor.add(new InstantiationStatement(beanId, rhs));
                } else { //the line must be a property configuration
                    processor.add(new AssignmentStatement(lhs, rhs));
                }
            }
            processor.execute();
        }
        LifecycleUtils.init(objects.values());
        return objects;
    }
    

    方法主要工作就是解析配置信息,传入参数即为配置信息健值对。对于配置文件中的基本类型配置,是不需要什么解析工作的,但是Shiro支持在配置文件配置复杂类型(类结构),同时支持在配置文件中设置类的属性。举个配置文件例子如下:

    [main]
    #authenticator
    authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
    authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
    authenticator.authenticationStrategy=$authenticationStrategy
    securityManager.authenticator=$authenticator
    
    #authorizer
    authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
    permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver
    authorizer.permissionResolver=$permissionResolver
    securityManager.authorizer=$authorizer
    
    #realm
    dataSource=com.alibaba.druid.pool.DruidDataSource
    dataSource.driverClassName=com.mysql.jdbc.Driver
    dataSource.url=jdbc:mysql://localhost:3306/shiro
    dataSource.username=root
    

    从配置文件可以看出Shiro的配置文件不是一些简单的基本类型配置,而是复杂类型的配置,authenticator和authorizer均为类对象,同时配置文件中还可以设置对象属性,总结一下,shiro配置文件支持三种配置方式:

    • 对象名 = 全限定类名 相对于调用 public 无参构造器创建对象
    • 对象名. 属性名 = 值 相当于调用 setter 方法设置常量值
    • 对象名. 属性名 =$ 对象引用 相当于调用 setter 方法设置对象引用

    那么shiro是怎么解析这些配置的了?

    这个就是上面方法的工作,方法中传入已经解析为健值对的配置信息,然后对key进行解析,根据其是否包含'.'符号,从而判断其是一个“对象”,还是“对象名. 属性名”,对于是“对象”的健,利用ClassLoader得到此对象的实例;

    processor.add(new InstantiationStatement(beanId, rhs));
    

    然后执行如下处理:

    protected void createNewInstance(Map<String, Object> objects, String name, String value) {
        Object instance = ClassUtils.newInstance(value);
        if (instance instanceof Nameable) {
            ((Nameable) instance).setName(name);
        }
        objects.put(name, instance);
    }
    

    对于“对象名. 属性名”的健,则利用BeanUtils设置对象的属性。

    processor.add(new AssignmentStatement(lhs, rhs));
    

    然后执行如下处理:

    protected Object doExecute() {
        String beanName = this.lhs;
        createNewInstance(objects, beanName, this.rhs);
        Object instantiated = objects.get(beanName);
        setBean(instantiated);
        enableEventsIfNecessary(instantiated, beanName);
        BeanEvent event = new InstantiatedBeanEvent(beanName, instantiated, Collections.unmodifiableMap(objects));
        eventBus.publish(event);
        return instantiated;
    }
    

    其中ClassUtils类是一个与Class相关的工具类,让我们可以很方便的操作类,比如说根据类名加载这个类(利用ClassLoader)等等。ClassUtils.newInstance(value)的定义如下:

    public static Object newInstance(String fqcn) {
        return newInstance(forName(fqcn));
    }
    

    此方法完成两步工作:
    1)根据类名加载此类
    2)创建一个新类的实例

    其中的forName方法即通过ClassLoader加载类,代码中定义了三种不同类型的ClassLoader,分别为THREAD_CL_ACCESSOR,CLASS_CL_ACCESSOR,SYSTEM_CL_ACCESSOR,默认为使用THREAD_CL_ACCESSOR,如果加载类失败,则使用CLASS_CL_ACCESSOR加载,还是失败则使用SYSTEM_CL_ACCESSOR,如果均失败,则抛出异常。

    public static Class forName(String fqcn) throws UnknownClassException {
        Class clazz = THREAD_CL_ACCESSOR.loadClass(fqcn);
        if (clazz == null) {
            clazz = CLASS_CL_ACCESSOR.loadClass(fqcn);
        }
        if (clazz == null) {
            clazz = SYSTEM_CL_ACCESSOR.loadClass(fqcn);
        }
        if (clazz == null) {
            throw new UnknownClassException(msg);
        }
        return clazz;
    }
    

    三个ClassLoaderAccessor的定义如下:

    private static final ClassLoaderAccessor THREAD_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
        @Override
        protected ClassLoader doGetClassLoader() throws Throwable {
            return Thread.currentThread().getContextClassLoader();
        }
    };
    
    private static final ClassLoaderAccessor CLASS_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
        @Override
        protected ClassLoader doGetClassLoader() throws Throwable {
            return ClassUtils.class.getClassLoader();
        }
    };
    
    private static final ClassLoaderAccessor SYSTEM_CL_ACCESSOR = new ExceptionIgnoringAccessor() {
        @Override
        protected ClassLoader doGetClassLoader() throws Throwable {
            return ClassLoader.getSystemClassLoader();
        }
    };
    

    newInstance(Class clazz)方法新建一个clazz的实例。

    public static Object newInstance(Class clazz) {
        return clazz.newInstance();
    }
    

    对于设置对对象属性,会用到Aache的BeanUtil类,这个类提供了方法获取类对象属性,以及设置对象属性等操作,有兴趣可以深入了解下Apache BeanUtils

    4、总结

    根绝配置文件初始化SecurityManager的过程为:
    1)解析配置文件为MAP健值对;
    2)创建一个默认的SecurityManager
    3)根据配置文件更新SecurityManager的成员和属性,其中感觉比较棒的几个操作为利用ClassLoader加载类实例,利用BeanUtils设置对象属性成功。

    工作顺利,天天开心!

    相关文章

      网友评论

        本文标题:SHIRO源码解读——SecurityManager创建

        本文链接:https://www.haomeiwen.com/subject/rsvjrctx.html