SHIRO源码解读——SecurityManager创建

一、初始化一个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设置对象属性成功。

工作顺利,天天开心!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

友情链接更多精彩内容