一、初始化一個SecurityManager
最簡單的初始化SecurityManager的方式如下:
DefaultSecurityManager securityManager= new DefaultSecurityManager();
但是,我們一般使用方式都是自定義一些配置,然后根據(jù)配置文件初始化SecurityManager,如下:
//解析ini文件為Ini對象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-config.ini");
//根據(jù)Ini對象初始化SecurityManager對象
SecurityManager securityManager = factory.getInstance();
那么Shiro是如何根據(jù)配置文件初始化一個SecurityManager的了?
根據(jù)配置文件初始化SecurityManager的主要分為兩部分:
- 解析Ini配置文件;
- 根據(jù)配置文件初始化一個SecurityManeger實例
二、解析Ini配置文件
IniSecurityManagerFactory類構(gòu)造函數(shù),傳入Ini配置文件路徑,然后將ini文件解析工作交給Ini的靜態(tài)方法fromResourcePath完成。
public IniSecurityManagerFactory(String iniResourcePath) {
this(Ini.fromResourcePath(iniResourcePath));
}
Ini解析完配置后,將結(jié)果返回,IniSecurityManagerFactory將解析后的Ini對象設(shè)置為自身持有。
public IniSecurityManagerFactory(Ini config) {
setIni(config);
}
這里有必要解析下Ini是什么?Ini是Shiro的配置數(shù)據(jù)結(jié)構(gòu)類,其內(nèi)部有一個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)獲取到文件流后,對其進行解析;
獲取文件流過程比較簡單,這里不做分析,主要看看如何進行解析的,執(zhí)行過程代碼片段:
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部分,根據(jù)[]規(guī)則
String newSectionName = getSectionName(line);
if (newSectionName != null) {
//此處代碼主要用于構(gòu)造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類都是實現(xiàn)了Map接口。Section持有的LinkedHashMap容器實際上是當前section中的所有鍵值對,而Ini持有的LinkedHashMap容器實際上是所有Section名稱與section對象的鍵值對。
上面代碼邏輯為:逐行讀取配置文件,遇到行內(nèi)容為"["開頭,"]"結(jié)尾,那么就是一個新的Section,接下來的內(nèi)容為此Section的內(nèi)容(按行分割),直到遇到下一個Section。
Section中將每行配置按照":"或"="分割成一個個key和value對最終形成 Map<String,String> props。
至此,Ini文件的解析已經(jīng)完成,其配置文件中的內(nèi)容已全部以map的形式存放在Ini實例中。
不過將配置文件解析為MAP,只是完成了第一步。使用過shiro的人都知道,shiro的配置項并不是簡單的基本類型,其配置項key可能是一個類對象,配置項value也可能是一個類對象,那么shiro是如何處理這些類對象的了?
在初始化SecurityManager時會對這些對象進行解析,后續(xù)會講到。之所以想總結(jié)本篇文章,其中很重要的一個點就是覺得shiro支持配置文件配置類結(jié)構(gòu)對象,覺得這種方式很棒,所以探究了下。
三、根據(jù)配置文件初始化SecurtiyManager
1、SecurityManager初始化時序圖和類圖
SecurityManager初始化時序圖如下:

SecurityManager初始化所涉及的類的類圖如下:

SecurityManager的主要初始化工作在InisecurityManagerFactory中完成,InisecurityManagerFactory利用了一些其他類或者工具來輔助完成初始化工作。比如利用ClassUtils類通過類名得到類實例,利用BeanUtil顯式設(shè)置對象屬性。
接下來看看初始化過程詳細分析。
2、SecurityManager初始化源碼詳細分析
IniSecurityManagerFactory
Factory,AbstractFactory,IniFactorySupport均是泛型類,層層繼承,IniSecurityManagerFactory是一個繼承IniFactorySupport的實例類,實例類型為SecurityManager,類的主要工作就是根據(jù)Ini配置初始化SecurityManager。
重點關(guān)注一下這個方法:
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));
此方法首先根據(jù)配置文件創(chuàng)建一個默認的SecurityManager,然后將此默認的SecurityManager的Bean存入ReflectionBuilder的Objects Map中,同時根據(jù)配置文件中是否含有roles或者users配置決定是否顯式創(chuàng)建Realm,創(chuàng)建Realm后也存入ReflectionBuilder的Objects Map中。
默認SecurityManager構(gòu)造時會進行如下初始化:
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);
根據(jù)main section配置intance一個新的securityManager實例,這一步比較復雜,主要是ReflectionBuilder類的工作,后面會單獨講一下ReflectionBuilder類。
第三步:
上面一二兩步余下代碼為第三步內(nèi)容,第三步主要是判斷SecurityManager是否包含Realm,如果包含Realm,就將Realm賦給SecurityManger。
ReflectionBudiler
ReflectionBudiler類的主要工作就是根據(jù)配置文件的配置得到各個對象的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;
}
方法主要工作就是解析配置信息,傳入?yún)?shù)即為配置信息健值對。對于配置文件中的基本類型配置,是不需要什么解析工作的,但是Shiro支持在配置文件配置復雜類型(類結(jié)構(gòu)),同時支持在配置文件中設(shè)置類的屬性。舉個配置文件例子如下:
[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均為類對象,同時配置文件中還可以設(shè)置對象屬性,總結(jié)一下,shiro配置文件支持三種配置方式:
- 對象名 = 全限定類名 相對于調(diào)用 public 無參構(gòu)造器創(chuàng)建對象
- 對象名. 屬性名 = 值 相當于調(diào)用 setter 方法設(shè)置常量值
- 對象名. 屬性名 =$ 對象引用 相當于調(diào)用 setter 方法設(shè)置對象引用
那么shiro是怎么解析這些配置的了?
這個就是上面方法的工作,方法中傳入已經(jīng)解析為健值對的配置信息,然后對key進行解析,根據(jù)其是否包含'.'符號,從而判斷其是一個“對象”,還是“對象名. 屬性名”,對于是“對象”的健,利用ClassLoader得到此對象的實例;
processor.add(new InstantiationStatement(beanId, rhs));
然后執(zhí)行如下處理:
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設(shè)置對象的屬性。
processor.add(new AssignmentStatement(lhs, rhs));
然后執(zhí)行如下處理:
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相關(guān)的工具類,讓我們可以很方便的操作類,比如說根據(jù)類名加載這個類(利用ClassLoader)等等。ClassUtils.newInstance(value)的定義如下:
public static Object newInstance(String fqcn) {
return newInstance(forName(fqcn));
}
此方法完成兩步工作:
1)根據(jù)類名加載此類
2)創(chuàng)建一個新類的實例
其中的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();
}
對于設(shè)置對對象屬性,會用到Aache的BeanUtil類,這個類提供了方法獲取類對象屬性,以及設(shè)置對象屬性等操作,有興趣可以深入了解下Apache BeanUtils
4、總結(jié)
根絕配置文件初始化SecurityManager的過程為:
1)解析配置文件為MAP健值對;
2)創(chuàng)建一個默認的SecurityManager
3)根據(jù)配置文件更新SecurityManager的成員和屬性,其中感覺比較棒的幾個操作為利用ClassLoader加載類實例,利用BeanUtils設(shè)置對象屬性成功。
工作順利,天天開心!