Shiro源码分析(一)——初始化SecurityManager

2021SC@SDUSC

一.Demo代码

Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.ini.IniSecurityManagerFactory;
import org.apache.shiro.lang.util.Factory;
import org.apache.shiro.mgt.SecurityManager;

public class Quickstart {

    public static void main(String[] args) {
    
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();

        SecurityUtils.setSecurityManager(securityManager);

        System.exit(0);
    }
}

shiro.ini

[users]
# 用户名:root 密码:secret 角色:admin
root = secret, admin
# 用户名:guest 密码:guest 角色:guest
guest = guest, guest
[roles]
# admin拥有全部权限,用通配符‘*’表示
admin = *

二.抽象工厂模式创建SecurityManager实例

Shiro抽象工厂模式的UML图
从Shiro抽象工厂模式的UML图中可以看到,IniSecurityManagerFactory类继承了IniFactorySupport类。IniFactorySupport类对基于INI配置生成实例的工厂实现提供基本支持,它继承了AbstractFactory类,而AbstractFactory类是Factory接口的实现。

IniSecurityManagerFactory类是基于INI配置创建SecurityManager实例的工厂。IniSecurityManagerFactory类有三个构造方法,分别为无参、接收一个Ini对象、接收一个表示ini文件路径的字符串。demo代码中使用的是下图中第三个构造方法,即传入一个表示ini文件路径的字符串。
在这里插入图片描述
首先分析工厂的构造过程:

Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:myshiro.ini");

向构造方法中传入表示ini文件路径的字符串后,涉及的调用如下:

  1. 调用Ini类的静态方法fromResourcePath(),并将路径传入该方法中。根据给定路径创建一个新的Ini实例,加载ini文件中的数据。资源路径必须是ResourceUtils.getInputStreamForPath方法可解释的值。结果返回一个Ini实例。
    在这里插入图片描述
  2. 调用this()并传入fromResourcePath()返回的Ini对象。此时执行的是本类接收Ini对象作为参数的构造方法,它会使用this()调用本类的无参构造方法,再调用父类IniFactorySupport中的setIni()方法,并将接收到的Ini对象传进去,为父类中的私有Ini类型变量ini赋值。
    在这里插入图片描述在这里插入图片描述
  3. 调用本类无参构造方法时,会调用ReflectionBuilder类的无参构造方法,然后将返回的ReflectionBuilder对象赋值给类中的私有成员变量builder。ReflectionBuilder类是一个对象生成器。使用反射和Apache Commons BeanUtils来构造给定“属性-值”映射的对象。通常,它们来自Shiro INI配置,用于构造或修改SecurityManager、其依赖项和基于web的安全过滤器。
    在这里插入图片描述
  4. IniSecurityManagerFactory类的构造方法执行完毕后,工厂被成功创建。
    在这里插入图片描述
    接下来调用工厂的getInstance()方法,得到SecurityManager对象。
SecurityManager securityManager = factory.getInstance();

getInstance()是IniSecurityManagerFactory的父类AbstractFactory的方法:
在这里插入图片描述

工厂生产的对象类型用泛型T表示,在这个例子中是SecurityManager。在创建实例前,首先通过isSingleton()判断是否为单例模式。若是,则会先判断是否存在这个实例,存在则直接将已有的实例赋值给局部变量instance,不存在再进行创建;否则直接创建一个所需实例。
在这里插入图片描述

在本例中,初始化IniSecurityManagerFactory类时会先初始化其所有父类,因此AbstractFactory的无参构造方法会被执行,将布尔型成员变量singleton赋值为true,表示此为单例模式。此时尚未有SecurityManager实例,因此会调用createInstance()方法进行创建。在AbstractFactory中,createInstance()是一个抽象方法,由其唯一子类IniFactorySupport进行实现。它会根据INI配置一个新的对象实例,其工作方式是解析INI文件,如果不存在,就会通过createDefaultInstance()方法创建并返回一个默认的实例。
在这里插入图片描述
createInstance()的实现方式如下:

  1. 调用本类的resolveIni()方法,通过本类的getIni()方法获取Ini对象,如果对象是null或者空,就会尝试查找并加载默认路径的Ini,都没找到就会返回null。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  2. 通过CollectionUtils.isEmpty()静态方法检测ini对象是否为空。如果为空则调用createDefaultInstance()方法创建默认实例,如果调用后仍没有创建成功,则抛出IllegalStateException异常,表明该类的实现没有在Ini配置类为空或null的情况下返回一个默认的实例,并说明这是Factory接口必须实现的,请检查。ini对象不为空则调用createInstance()方法,并将ini对象传入,没有创建成功则会抛出IllegalStateException异常,表明该类的实现没有通过createInstance(Ini)方法返回构造的实例。
    在Demo中,ini对象不为空,会调用createInstance(Ini)方法创建实例。
  3. 带有参数的createInstance(Ini)方法和createDefaultInstance()方法在IniFactorySupport类中为抽象方法,在子类IniSecurityManagerFactory中实现。
    在这里插入图片描述
    createDefaultInstance()方法会调用DefaultSecurityManager类的无参构造方法,返回一个默认的SecurityManager实例。
    在这里插入图片描述
    createInstance(Ini)方法会先检查传入的Ini对象是否为空,是则抛出NullPointerException,否则继续执行,调用本类的createSecurityManager(Ini)方法,传入Ini对象。在该方法中调用它的重载createSecurityManager(Ini, Section)。
    在这里插入图片描述
    在这里插入图片描述

重载的方法首先会调用createDefaults(Ini, Section),然后调用getReflectionBuilder()获得在初始化这个类时创建好的ReflectionBuilder实例,再调用这个实例的setObjects()方法传入createDefaults(Ini, Section)返回的Map对象。
在createDefaults(Ini, Section)方法中,首先会创建一个LinkedHashMap并将其赋值给局部变量defaults,它可以理解为双向链表和HashMap的结合,实现了有序的迭代。然后调用createDefaultInstance()方法创建一个新的SecurityManager实例,创建完成后将其放入LinkedHashMap中。接着调用shouldImplicitlyCreateRealm(Ini)方法判断ini文件中是否包含账户数据以及是否需要隐式创建的Realm。若需要,则会调用createRealm(Ini)方法根据Ini实例中包含的账户数据创建一个Realm。
在这里插入图片描述
创建成功后则会将Realm的名称和其实例放入LinkedHashMap中。然后调用父类IniFactorySupport的getDefaults()方法获取用“字符串-bean”映射的工厂的默认对象集,工厂可以将这些bean与从INI配置解析的对象结合使用。接下来就是遍历defaultBeans将其全部放入defaults中然后返回,传递给ReflectionBuilder实例的setObjects(Map<String, ?>)方法。
在这里插入图片描述
接着就是调用buildInstances(Section)将各种对象绑定到SecurityManager上
在这里插入图片描述
在这里插入图片描述
然后通过getSecurityManagerBean()方法获取SecurityManager实例,准备对其中的Realm进行配置。
在这里插入图片描述
在这里插入图片描述
再调用isAutoApplyRealms(SecurityManager)判断是否自动应用realm。
由于在shiro.ini中指定了users,前面创建了IniRealm,因此该方法返回的值为true
在这里插入图片描述
然后调用getRealms()方法从objects中将Realm取出来放入集合中,判断集合中是否为空后调用applyRealmsToSecurityManager()将Realm和SecurityManager绑定,完成后返回创建好的SecurityManager。
在这里插入图片描述
时序图
在这里插入图片描述
得到SecurityManager对象后调用SecurityUtils的静态方法setSecurityManager()从而能够在后续代码中使用getSubject()实现。
在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值