Shiro学习(二)——读取配置文件与初始化SecurityManager的过程

概述

本文属于源码分析,只想了解Shiro使用的读者请略过。

Shiro学习(一)——Shiro配置与快速开始》提出了几个问题。本文要分析:

1、ini文件是怎么读入并被初始化?

2、securityManager具体是用哪个实现类,是怎么初始化得来的?

3、默认Realm是什么,怎么来的?

 

读取配置文件

在Shiro使用的例子中,最开始一般都会调用以下语句:

Factory<org.apache.shiro.mgt.SecurityManager> factory =
                new IniSecurityManagerFactory("classpath:shiro.ini");

尽管官方推荐使用Environment,我们这里仍然用IniSecurityManagerFactory来举例,因为Environment内部也是调用IniSecurityManagerFactory并且通过Environment初始化,同时初始化SecurityManager,我希望分开两步来讲。查看下BasicIniEnvironment.java:

public class BasicIniEnvironment extends DefaultEnvironment {

    public BasicIniEnvironment(Ini ini) {
        setSecurityManager(new IniSecurityManagerFactory(ini).getInstance());
    }

    public BasicIniEnvironment(String iniResourcePath) {
        this(Ini.fromResourcePath(iniResourcePath));
    }
}

可以看到3-5行实际上等于调用了创建IniSecurityManagerFactory并且初始化SecurityManager。

不谈Environment,回到IniSecurityManagerFactory上。看看IniSecurityManagerFactory的初始化

public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {

//......

    public IniSecurityManagerFactory() {
        this.builder = new ReflectionBuilder();
    }

    public IniSecurityManagerFactory(Ini config) {
        this();
        setIni(config);
    }

    public IniSecurityManagerFactory(String iniResourcePath) {
        this(Ini.fromResourcePath(iniResourcePath));
    }

//......
}

13-15行是例子一般调用的构造方法,构造方法很简单,就是根据配置文件初始化Ini类,然后设置到IniSecurityManagerFactory的成员变量上。所以这个构造方法的核心是通过配置文件构造Ini类。我们从Ini.fromResourcePath(iniResourcePath)跟踪下去:

public class Ini implements Map<String, Ini.Section> {

    public static final String DEFAULT_SECTION_NAME = ""; //empty string means the first unnamed section
    public static final String DEFAULT_CHARSET_NAME = "UTF-8";
    
    public static final String COMMENT_POUND = "#";
    public static final String COMMENT_SEMICOLON = ";";
    public static final String SECTION_PREFIX = "[";
    public static final String SECTION_SUFFIX = "]";

    protected static final char ESCAPE_TOKEN = '\\';

    private final Map<String, Section> sections;

//......

    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 = new Ini();
        ini.loadFromPath(resourcePath);
        return ini;
    }

//......

    private void addSection(String name, StringBuilder content) {
        if (content.length() > 0) {
            String contentString = content.toString();
            String cleaned = StringUtils.clean(contentString);
            if (cleaned != null) {
                Section section = new Section(name, contentString);
                if (!section.isEmpty()) {
                    sections.put(name, section);
                }
            }
        }
    }

    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);

            if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
                continue;
            }

            String newSectionName = getSectionName(line);
            if (newSectionName != null) {
                addSection(sectionName, sectionContent);
                sectionContent = new StringBuilder();
                sectionName = newSectionName;
            } else {
                sectionContent.append(rawLine).append("\n");
            }
        }

        addSection(sectionName, sectionContent);
    }

//......
}

21行新建Ini类,22行通过读取配置文件进行设置。这其中获取输入流涉及层层调用,这里不叙述这些调用,直接到41行的load通过Scanner读取配置文件。45行开始逐行循环调用,47行读取一行文本,50行判断若为null或者前缀为#或者//的话,则略过这行。54获取sectionName,sectionName就是“[XXX]”里面的东西,例如上一篇文章中的[user]和[main]里面的user和main就是sectionName。55行判断若该行为sectionName,56-58行则把前一个sectionName及其内容进行保存,若该行不为sectionName,则表示该行是sectionName下面的key-value配置,增加到sectionContent字符串中。

28-39行为新增Section操作。若传入的content经过去出前后空格操作后不为空,则33行创建一个Section,35行保存至Ini类的map中。

那Section又是什么呢?它是Ini类的内部类,我们跟踪看看创建Section的情况:

public static class Section implements Map<String, String> {
    private final String name;
    private final Map<String, String> props;

    //......

    private Section(String name, String sectionContent) {
        if (name == null) {
            throw new NullPointerException("name");
        }
        this.name = name;
        Map<String,String> props;
        if (StringUtils.hasText(sectionContent) ) {
            props = toMapProps(sectionContent);
        } else {
            props = new LinkedHashMap<String,String>();
        }
        if ( props != null ) {
            this.props = props;
        } else {
            this.props = new LinkedHashMap<String,String>();
        }
    }

    //......

    private static Map<String, String> toMapProps(String content) {
        Map<String, String> props = new LinkedHashMap<String, String>();
        String line;
        StringBuilder lineBuffer = new StringBuilder();
        Scanner scanner = new Scanner(content);
        while (scanner.hasNextLine()) {
            line = StringUtils.clean(scanner.nextLine());
            if (isContinued(line)) {
                line = line.substring(0, line.length() - 1);
                lineBuffer.append(line);
                continue;
            } else {
                lineBuffer.append(line);
            }
            line = lineBuffer.toString();
            lineBuffer = new StringBuilder();
            String[] kvPair = splitKeyValue(line);
            props.put(kvPair[0], kvPair[1]);
        }

        return props;
    }
//......
}

14行调用toMapProps方法,把Section下的sectionContent字符串内容变为map形式的key-value。32行开始逐行循环读取sectionContent字符串,34行的isContinued判断行末是否为"\\",即该行还没结束,是的话则继续读取下一行,否则该行就是一条key-value。43行splitKeyValue作用是把字符串按“:”或“=”进行分割,44行分别作为key和value存入props中。

总的来说,就是把ini文件中[XXX]里面的内容作为sectionName,sectionName下面的内容作为key-value保存到Section类的props属性中。可以写一段测试程序测试一下:

@Test
public void testIni() {
	Collection<Section> sections = Ini.fromResourcePath("classpath:shiro-jdbc-realm.ini").getSections();
	Iterator<Section> iterator = sections.iterator();
	while(iterator.hasNext()) {
		Section section = iterator.next();
		String sectionName = section.getName();
		System.out.println("sectionName:" + sectionName);
		Set<String> set = section.keySet();
		for(String prop : set) {
			System.out.println("key:" + prop + "\t" + "value:" + section.get(prop));
		}
	}
}

这里classpath:shiro-jdbc-realm.ini就是《Shiro学习(一)——Shiro配置与快速开始》中数据库ini配置,执行后控制台输出:

sectionName:main
key:jdbcRealm	value:org.apache.shiro.realm.jdbc.JdbcRealm
key:dataSource	value:com.alibaba.druid.pool.DruidDataSource
key:dataSource.driverClassName	value:com.mysql.jdbc.Driver
key:dataSource.url	value:jdbc:mysql://localhost:3306/test
key:dataSource.username	value:root
key:jdbcRealm.dataSource	value:$dataSource
key:securityManager.realms	value:$jdbcRealm

可以看到Ini.fromResourcePath这个操作就是把ini文件读入并按key-value保存好。

 

创建SecurityManager

读取ini配置之后,下一个步骤就是要生成SecurityManager。例子通过以下代码实现:

org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

显然要跟踪factory.getInstance()方法。这里会调用AbstractFactory的getInstance:

public abstract class AbstractFactory<T> implements Factory<T> {

    private boolean singleton;
    private T singletonInstance;

    public AbstractFactory() {
        this.singleton = true;
    }

    public boolean isSingleton() {
        return singleton;
    }

    public void setSingleton(boolean singleton) {
        this.singleton = singleton;
    }

    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;
    }

    protected abstract T createInstance();
}

可以看到AbstractFactory默认构造方法设置singleton为true,即单例模式。20-27行根据singleton判断是否按单例模式创建SecurityManager实,createInstance方法就是具体创建方法,这里调用的是IniFactorySupport的createInstance:

public abstract class IniFactorySupport<T> extends AbstractFactory<T> {

//......

    protected Ini resolveIni() {
        Ini ini = getIni();
        if (CollectionUtils.isEmpty(ini)) {
            log.debug("Null or empty Ini instance.  Falling back to the default {} file.", DEFAULT_INI_RESOURCE_PATH);
            ini = loadDefaultClassPathIni();
        }
        return ini;
    }

    public T createInstance() {
        Ini ini = resolveIni();

        T instance;

        if (CollectionUtils.isEmpty(ini)) {
            instance = createDefaultInstance();
            if (instance == null) {
                String msg = getClass().getName() + " implementation did not return a default instance in " +
                        "the event of a null/empty Ini configuration.  This is required to support the " +
                        "Factory interface.  Please check your implementation.";
                throw new IllegalStateException(msg);
            }
        } else {
            instance = createInstance(ini);
            if (instance == null) {
                String msg = getClass().getName() + " implementation did not return a constructed instance from " +
                        "the createInstance(Ini) method implementation.";
                throw new IllegalStateException(msg);
            }
        }

        return instance;
    }
//......
}

19行若ini为空,则调用createDefaultInstance,这个调用最终会创建DefaultSecurityManager。若存在ini(例子中我们存在),则调用28行,这里会调用IniSecurityManagerFactory的createSecurityManager:

public class IniSecurityManagerFactory extends IniFactorySupport<SecurityManager> {

//......

    protected SecurityManager createDefaultInstance() {
        return new DefaultSecurityManager();
    }

    protected SecurityManager createInstance(Ini ini) {
        if (CollectionUtils.isEmpty(ini)) {
            throw new NullPointerException("Ini argument cannot be null or empty.");
        }
        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) {
        return createSecurityManager(ini, getConfigSection(ini));
    }

//......

    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) {
            Collection<Realm> realms = getRealms(objects);
            if (!CollectionUtils.isEmpty(realms)) {
                applyRealmsToSecurityManager(realms, securityManager);
            }
        }

        return securityManager;
    }

    protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
        Map<String, Object> defaults = new LinkedHashMap<String, Object>();

        SecurityManager securityManager = createDefaultInstance();
        defaults.put(SECURITY_MANAGER_NAME, securityManager);

        if (shouldImplicitlyCreateRealm(ini)) {
            Realm realm = createRealm(ini);
            if (realm != null) {
                defaults.put(INI_REALM_NAME, realm);
            }
        }

        // The values from 'getDefaults()' will override the above.
        Map<String, ?> defaultBeans = getDefaults();
        if (!CollectionUtils.isEmpty(defaultBeans)) {
            defaults.putAll(defaultBeans);
        }

        return defaults;
    }

//......
}

首先调用22行,然后跳到27行。29行createDefaults创建SecurityManager并设置到ReflectionBuilder中保存。39行把Realm设置到SecurityManager中。跟踪createDefaults看它如何创建SecurityManager,49行调用createDefaultInstance,跳到第5行,第6行就是我们要找的,新建DefaultSecurityManager!

 

默认Realm的创建

Realm是什么?读者可以理解为它就是一个获取用户帐号密码信息与授权信息,用于后续比对的一个模块。有默认的实现方式,我们也可以自定义,例如我们把用户信息和授权信息放在数据库,具体要查哪个表,我们就自定义Realm去实现。

上面一节也提到了会把Realm设置到SecurityManager中,那么默认的Realm又是什么?我们跟踪IniSecurityManagerFactory里面createSecurityManager方法中:

public class IniSecurityManagerFactory extends IniFactorySupport<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) {
            Collection<Realm> realms = getRealms(objects);
            if (!CollectionUtils.isEmpty(realms)) {
                applyRealmsToSecurityManager(realms, securityManager);
            }
        }

        return securityManager;
    }

    protected Map<String, ?> createDefaults(Ini ini, Ini.Section mainSection) {
        Map<String, Object> defaults = new LinkedHashMap<String, Object>();

        SecurityManager securityManager = createDefaultInstance();
        defaults.put(SECURITY_MANAGER_NAME, securityManager);

        if (shouldImplicitlyCreateRealm(ini)) {
            Realm realm = createRealm(ini);
            if (realm != null) {
                defaults.put(INI_REALM_NAME, realm);
            }
        }

        Map<String, ?> defaultBeans = getDefaults();
        if (!CollectionUtils.isEmpty(defaultBeans)) {
            defaults.putAll(defaultBeans);
        }

        return defaults;
    }

//......

    protected boolean shouldImplicitlyCreateRealm(Ini ini) {
        return !CollectionUtils.isEmpty(ini) &&
                (!CollectionUtils.isEmpty(ini.getSection(IniRealm.ROLES_SECTION_NAME)) ||
                        !CollectionUtils.isEmpty(ini.getSection(IniRealm.USERS_SECTION_NAME)));
    }

    protected Realm createRealm(Ini ini) {
        IniRealm realm = new IniRealm();
        realm.setName(INI_REALM_NAME);
        realm.setIni(ini);
        return realm;
    }

//......
}

第7行调用createDefaults方法,跳到24行。30行shouldImplicitlyCreateRealm判断是否需要隐式创建Realm,其判断依据是ini文件中包含名称为users或roles的sectionName。显然例子的ini中,我们包含了users,因此31行调用createRealm,跳到53行,54行创建IniRealm,这就是我们要找的东西!

 

小结

本文主要是针对上一篇文章提出的三个问题进行了一些研究。总结就是:

1、ini文件中的信息,[XXX]会以sectionName作为key,其下的配置以Section类作为value保存。其Section又是以key-value形式保存这些配置属性;

2、securityManager的具体实现类是DefaultSecurityManager;

3、默认Realm是IniRealm。

当然上面总结的是以最简单的例子默认创建securityManager、Realm的情况来说明。从中我们也分析了初始化大致的来龙去脉。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值