SHIRO源碼解讀——SecurityManager創(chuàng)建

一、初始化一個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初始化所涉及的類的類圖如下:


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è)置對象屬性成功。

工作順利,天天開心!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容