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设置对象属性成功。

工作顺利,天天开心!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容