一、初始化一个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设置对象属性成功。
工作顺利,天天开心!
网友评论