Shiro源码分析-初始化-SecurityManager
程序员文章站
2022-04-16 21:05:30
...
开涛的《跟我学Shiro》系列已即将完成,该系列囊括了shiro的绝大部分实用功能,并且在讲解如何用的过程中,也添加了其内部实现的原理。开涛主要以Shiro的使用者为角度,所以其原理部分是穿插在各个章节内的。
源码分析的第一篇以SecurityManager的初始化为题。
根据ini配置文件初始化shiro的代码主要为两段:
ini文件格式说明请参考:
http://zh.wikipedia.org/wiki/INI%E6%AA%94%E6%A1%88
java解析ini的方式也比较多,有兴趣可以参考:
http://my.oschina.net/tinyframework/blog/214309
一、Shiro解析ini的步骤如下:
1、org.apache.shiro.config.IniSecurityManagerFactory类构造方法:
将ini文件解析交给Ini的静态方法fromResourcePath完成。并把解析后的Ini对象设置由自身持有
2、org.apache.shiro.config.Ini类解析逻辑:
ResourceUtils.getInputStreamForPath(resourcePath);这里支持三种获取资源的方式:
3、对获取的资源输入流最终交给文本扫描器Scaner,执行过程代码片段:
上段代码主要是组装ini文件中的section。细心的同学会发现Section、Ini都是实现了Map接口。Section持有的LinkedHashMap容器实际上是当前section中的所有键值对,而Ini持有的LinkedHashMap容器实际上是所有section名称与section对象的键值对。
至此,ini文件的解析已经完成,其ini中的内容已全部以map的形式存放在Ini实例中。
至于保存的结果是什么样的呢?以下面的一段配置为例:
Ini的sections属性[key=main,value=Section对象],此Section对象内部的props保存了所有main部分key-value映射。目前都是String类型,实例化在下一步完成的。
二、由Ini实例构造SecurityManager对象
IniSecurityManagerFactory的继承关系为:
IniSecurityManagerFactory->IniFactorySupport->AbstractFactory->Factory
这里的getInstance方法由最上级的抽象类:org.apache.shiro.util.AbstractFactory提供,源码如下:
IniFactorySupport的createInstance实现如下(省略了无关紧要的日志、判断代码):
上面两个抽象方法,则交给实现类IniSecurityManagerFactory完成了。
先阅读createDefaultInstance方法源码
终于在这个不起眼的地方,看到了初始化最核心的接口SecurityManager了。稍微暂停一下,发个SecurityManager的类图:
由此图可以看出来,SecurityManager继承了三个接口(认证、授权、session管理),认证授权是安全框架最核心的功能,而shiro提供了自身的session管理机制(这也是shiro的一大亮点)。图中除了DefaultSecurityManager,其它所有类都是抽象类,由此可看出,DefaultSecurityManager是作为默认的安全管理器。
再来看new DefaultSecurityManager();做的事情
这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。
在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。需要注意的是,RealmSecurityManager、CachingSecurityManager并没有设置默认的对象,所以这个是交给开发人员自己配置的。
接下来看createInstance实现细节:
再看看ReflectionBuilder如何对main进行实例化及依赖的设置:
SecurityManager的初始化部分已经完成。
源码分析的第一篇以SecurityManager的初始化为题。
根据ini配置文件初始化shiro的代码主要为两段:
//解析ini文件为Ini对象 Factory<SecurityManager> factory = new IniSecurityManagerFactory( "classpath:shiro-config.ini"); //根据Ini对象初始化SecurityManager对象 SecurityManager securityManager = factory.getInstance();
ini文件格式说明请参考:
http://zh.wikipedia.org/wiki/INI%E6%AA%94%E6%A1%88
java解析ini的方式也比较多,有兴趣可以参考:
http://my.oschina.net/tinyframework/blog/214309
一、Shiro解析ini的步骤如下:
1、org.apache.shiro.config.IniSecurityManagerFactory类构造方法:
public IniSecurityManagerFactory(String iniResourcePath) { this(Ini.fromResourcePath(iniResourcePath)); }
将ini文件解析交给Ini的静态方法fromResourcePath完成。并把解析后的Ini对象设置由自身持有
public IniSecurityManagerFactory(Ini config) { setIni(config); }
2、org.apache.shiro.config.Ini类解析逻辑:
public static Ini fromResourcePath(String resourcePath) throws ConfigurationException { if (!StringUtils.hasLength(resourcePath)) { throw new IllegalArgumentException("Resource Path argument cannot be null or empty."); } //此处新建Ini对象 Ini ini = new Ini(); ini.loadFromPath(resourcePath); return ini; } //根据资源路径获取输入流,交给load方法进行处理 public void loadFromPath(String resourcePath) throws ConfigurationException { InputStream is; try { is = ResourceUtils.getInputStreamForPath(resourcePath); } catch (IOException e) { throw new ConfigurationException(e); } load(is); }
ResourceUtils.getInputStreamForPath(resourcePath);这里支持三种获取资源的方式:
classpath | shiro-config.ini | 从类路径中查找ini配置 |
url | http://....../shiro-config.ini | 从指定的url获取ini配置 |
file | D:\shiro-config.ini | 从指定的文件路径获取ini配置 |
3、对获取的资源输入流最终交给文本扫描器Scaner,执行过程代码片段:
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); //reset the buffer for the new section: 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对象的键值对。
至此,ini文件的解析已经完成,其ini中的内容已全部以map的形式存放在Ini实例中。
至于保存的结果是什么样的呢?以下面的一段配置为例:
[main] #authenticator authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy authenticator.authenticationStrategy=$authenticationStrategy securityManager.authenticator=$authenticator
Ini的sections属性[key=main,value=Section对象],此Section对象内部的props保存了所有main部分key-value映射。目前都是String类型,实例化在下一步完成的。
二、由Ini实例构造SecurityManager对象
SecurityManager securityManager = factory.getInstance();
IniSecurityManagerFactory的继承关系为:
IniSecurityManagerFactory->IniFactorySupport->AbstractFactory->Factory
这里的getInstance方法由最上级的抽象类:org.apache.shiro.util.AbstractFactory提供,源码如下:
//该方法只是用于判断是否单例(默认为单例) 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();
IniFactorySupport的createInstance实现如下(省略了无关紧要的日志、判断代码):
public T createInstance() { //此处获取解析ini文件产生的Ini对象 Ini ini = resolveIni(); T instance; if (CollectionUtils.isEmpty(ini)) { //如果ini为空,则创建默认实例 instance = createDefaultInstance(); } else { //根据ini对象创建实例 instance = createInstance(ini); } return instance; } protected abstract T createInstance(Ini ini); protected abstract T createDefaultInstance();
上面两个抽象方法,则交给实现类IniSecurityManagerFactory完成了。
先阅读createDefaultInstance方法源码
protected SecurityManager createDefaultInstance() { return new DefaultSecurityManager(); }
终于在这个不起眼的地方,看到了初始化最核心的接口SecurityManager了。稍微暂停一下,发个SecurityManager的类图:
由此图可以看出来,SecurityManager继承了三个接口(认证、授权、session管理),认证授权是安全框架最核心的功能,而shiro提供了自身的session管理机制(这也是shiro的一大亮点)。图中除了DefaultSecurityManager,其它所有类都是抽象类,由此可看出,DefaultSecurityManager是作为默认的安全管理器。
再来看new DefaultSecurityManager();做的事情
public DefaultSecurityManager() { super(); this.subjectFactory = new DefaultSubjectFactory(); this.subjectDAO = new DefaultSubjectDAO(); }
这里暂时不深究每个构造器所做的具体事情。有兴趣的同学,可一直观察DefaultSecurityManager的所有父类构造器的处理逻辑。
类名称 | 构造方法创建的默认对象 |
DefaultSecurityManager | DefaultSubjectFactory,DefaultSubjectDAO |
SessionsSecurityManager | DefaultSessionManager |
AuthorizingSecurityManager | ModularRealmAuthorizer |
AuthenticatingSecurityManager | ModularRealmAuthenticator |
RealmSecurityManager | 无 |
CachingSecurityManager | 无 |
在上面的表格中已经清晰的描述了各个类所设置的默认对象,至于用途后面再讲解。需要注意的是,RealmSecurityManager、CachingSecurityManager并没有设置默认的对象,所以这个是交给开发人员自己配置的。
接下来看createInstance实现细节:
protected SecurityManager createInstance(Ini ini) { if (CollectionUtils.isEmpty(ini)) { throw new NullPointerException("Ini argument cannot be null or empty."); } //SecurityManager的创建工作交给createSecurityManager方法 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) { //单独获取main部分进行初始化 Ini.Section mainSection = ini.getSection(MAIN_SECTION_NAME); if (CollectionUtils.isEmpty(mainSection)) { //try the default: mainSection = ini.getSection(Ini.DEFAULT_SECTION_NAME); } return createSecurityManager(ini, mainSection); } private SecurityManager createSecurityManager(Ini ini, Ini.Section mainSection) { //创建DefaultSecurityManager,并且根据ini初始化realm Map<String, ?> defaults = createDefaults(ini, mainSection); //根据main部分构造对象(包括属性依赖,创建实例等) Map<String, ?> objects = buildInstances(mainSection, defaults); //从ReflectionBuilder的objects获取SecurityManager实例。ReflectionBuilder负责shiro的所有反射构造实例的工作,并且把实例放在objects的集合中持有。 SecurityManager securityManager = getSecurityManagerBean(); //如果显示指定了realm,则不会自动配置realm。 boolean autoApplyRealms = isAutoApplyRealms(securityManager); if (autoApplyRealms) { //如果在ini文件中未显示指定realm(即:securityManager.realms=$myRealm1) //那么ini配置的所有realm会自动设置(即:realm1=test.Realm1) //getRealms方法只是把ini配置的所有实例对象中实现Realm、RealmFactory接口的实例添加到SecurityManager中 Collection<Realm> realms = getRealms(objects); //set them on the SecurityManager if (!CollectionUtils.isEmpty(realms)) { applyRealmsToSecurityManager(realms, securityManager); } } return securityManager; } //创建默认的SecurityManager protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) { Map<String, Object> defaults = new LinkedHashMap<String, Object>(); //这里还是先创建了DefaultSecurityManager SecurityManager securityManager = createDefaultInstance(); defaults.put(SECURITY_MANAGER_NAME, securityManager); //判断ini是否配置了roles、users if (shouldImplicitlyCreateRealm(ini)) { //如果配置了roles或users,则创建IniRealm Realm realm = createRealm(ini); if (realm != null) { defaults.put(INI_REALM_NAME, realm); } } return defaults; } //对main部分配置的所有类型、属性进行实例化,并设置依赖,放到objects集合中 private Map<String, ?> buildInstances(Ini.Section section, Map<String, ?> defaults) { this.builder = new ReflectionBuilder(defaults); return this.builder.buildObjects(section); }
再看看ReflectionBuilder如何对main进行实例化及依赖的设置:
public Map<String, ?> buildObjects(Map<String, String> kvPairs) { if (kvPairs != null && !kvPairs.isEmpty()) { //实例配置集合 Map<String, String> instanceMap = new LinkedHashMap<String, String>(); //属性依赖配置集合 Map<String, String> propertyMap = new LinkedHashMap<String, String>(); for (Map.Entry<String, String> entry : kvPairs.entrySet()) { //如果ini配置的key包括.号,或者以.class后缀的名称,则认为类型配置 if (entry.getKey().indexOf('.') < 0 || entry.getKey().endsWith(".class")) { instanceMap.put(entry.getKey(), entry.getValue()); //其它情况,则认为是属性依赖配置 } else { propertyMap.put(entry.getKey(), entry.getValue()); } } // Create all instances for (Map.Entry<String, String> entry : instanceMap.entrySet()) { //实例化每个类型 createNewInstance((Map<String, Object>) objects, entry.getKey(), entry.getValue()); } // Set all properties for (Map.Entry<String, String> entry : propertyMap.entrySet()) { //配置所有的依赖。必须有个先后顺序,先实例化再设置依赖 applyProperty(entry.getKey(), entry.getValue(), objects); } } //SHIRO-413: init method must be called for constructed objects that are Initializable //如果配置的类型实现了Initializable接口,则调用其init方法 LifecycleUtils.init(objects.values()); return objects; }
SecurityManager的初始化部分已经完成。
推荐阅读
-
spring源码分析系列5:ApplicationContext的初始化与Bean生命周期
-
spring源码分析6: ApplicationContext的初始化与BeanDefinition的搜集入库
-
Spring源码分析之IoC容器初始化
-
ThinkPHP6源码分析之应用初始化
-
Bootstrap初始化过程源码分析--netty客户端的启动
-
SpringMVC源码分析--DispatcherServlet初始化的九大组件的总体概述(三)
-
Tomcat初始化源码流程分析(图解及源码注释) (一)
-
SpringBoot内置tomcat启动原理、以及SpringBoot初始化Servlet的源码分析
-
tomcat源码分析-Container初始化与加载
-
Guzz源码分析(一) guzz容器初始化过程