CsrfGuardServletContextListener分析
一、整体框架介绍
首先我们看一下 csrfguard 这个jar 的整体框架 如下图, 里面主要 是分 config 配置, Filter , listener ,action 和 resources 下面的 默认的
csrfguard.properties 和 csrfguard.js 文件
二、CsrfGuardServletContextListener 分析
我们从 Web.xml 入手, 首先第一个加载的是 CsrfGuardServletContextListener 这个 类.
CsrfGuardServletContextListener.class 这个类 实现了 ServletContextListener 接口, 而 ServletContextListener 接口 里面一共就 两个方法:
public interface ServletContextListener extends EventListener {
public void contextInitialized ( ServletContextEvent sce );
public void contextDestroyed ( ServletContextEvent sce );
}
这两个方法分别再 Web 应用 启动和 销毁的时候 运行,接下来主要看一下这两个方法
2.1 contextInitialized 解析
这里contextInitialized 的主要逻辑就是 进行 加载 文件 ,
-
文件名称(名称可以包含具体路径) 可以在web.xml 的 里面进行配置 , key 为 Owasp.CsrfGuard.Config
-
配置的路径 有3种 ,第一种 在 classes 目录下面, 第二种在 context 下面 , 第三种 直接 配置绝对路径
-
如果 没有配置 名称,默认Owasp.CsrfGuard.properties , 进行全局搜索并 加载
-
如果还是没有找到, 那就获取 jar 包下面的 “META-INF/csrfguard.properties” 文件,默认配置
-
进行 日志打印 , 这里 也是 先从 获取配置是否需要打印, 如果没有配置 ,再次获取 config 配置里面的(配置文件里面 默认是 true ,org.owasp.csrfguard.Config.Print = true) , 同时注意了, 这里 不是用的 log4j 进行打印日志,而是 context .log ,这里打印的日志 会在tomcat 的 conf 下面配置的 log.properties 里面对应的路径 , 感觉这样有点不太好啊 ,下面主要分析一下 CsrfGuard 这个类
@Override
public void contextInitialized(ServletContextEvent event) {
// 获取到ServletContext
ServletContext context = event.getServletContext();
// 获取当前项目的context路径
servletContext = context.getContextPath();
// servletcontext 仅仅是一个路径的前置, 如果没有ContextPath,那就置为空字符串
// 如果存在 ContextPath,那就跳过
if (servletContext == null || "/".equals(servletContext)) {
servletContext = "";
}
// 从context-param 中 获取 对应的 key(Owasp.CsrfGuard.Config) 对应的value
//这里是 获取配置文件的路径,便于后面进行加载
configFileName = context.getInitParameter(CONFIG_PARAM);
// 如果没有配置 Owasp.CsrfGuard.Config 对应的 路径,那就设置默认的
// 默认文件名 就是 Owasp.CsrfGuard.properties
if (configFileName == null) {
configFileName = ConfigurationOverlayProvider.OWASP_CSRF_GUARD_PROPERTIES;
}
InputStream is = null;
Properties properties = new Properties();
try {
/** 对文件进行加载
这块的逻辑也比较简单
1. 从 当前的 classpath 路径下面 ,通过 getResourceAsStream 进行加载
2. 从 web context 目录开始 寻找
3. 直接从当前目录开始寻找,这里就是配置了 绝对路径
**/
is = getResourceStream(configFileName, context, false);
// 如果is 为null ,那就获取默认的 配置
// 默认的配置,在csrfguard jar 下面的 META-INF/csrfguard.properties
if (is == null) {
// 这里有一个问题,就是如果走到这一步, 说明 configFileName不对, 但是 configFileName 后面在其他类里面也用到,
// 是不是应该把configFileName 改成这里的"META-INF/csrfguard.properties",当然这个只是异常情况
is = getResourceStream(ConfigurationOverlayProvider.META_INF_CSRFGUARD_PROPERTIES, context, false);
}
//如果为空,直接报错
if (is == null) {
throw new RuntimeException("Cant find default owasp csrfguard properties file: " + configFileName);
}
//进行load 并赋值, 这里的 CsrfGuard 是单例模式, 后续的 properties都是共享的
properties.load(is);
CsrfGuard.load(properties);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Streams.close(is);
}
// 进行打印日志 并添加前缀
printConfigIfConfigured(context, "Printing properties before Javascript servlet, note, the javascript properties might not be initialized yet: ");
}
private InputStream getResourceStream(String resourceName, ServletContext context, boolean failIfNotFound) throws IOException {
InputStream is = null;
/** try classpath
从当前classpath 路径开始 寻找,位于 WEB-INF 下面的classes
**/
is = getClass().getClassLoader().getResourceAsStream(resourceName);
/** try web context
从context 目录下直接寻找
**/
if (is == null) {
String fileName = context.getRealPath(resourceName);
File file = new File(fileName);
if (file.exists()) {
is = new FileInputStream(fileName);
}
}
/** try current directory
从单前目录开始寻找,用这种方式,需要 将路径配置为绝对路径
**/
if (is == null) {
File file = new File(resourceName);
if (file.exists()) {
is = new FileInputStream(resourceName);
}
}
/** fail if still empty
如果还没有找到, 根据failIfNotFound 确定是 直接报错,还是返回null
**/
if (is == null && failIfNotFound) {
throw new IOException(String.format("unable to locate resource - %s", resourceName));
}
return is;
}
public static void printConfigIfConfigured(ServletContext context, String prefix) {
// 从 <context-param> 获取 key("Owasp.CsrfGuard.Config.Print") 对应的value
String printConfig = context.getInitParameter(CONFIG_PRINT_PARAM);
/** 如果在 <context-param> 里面没有配置,那就从 config 文件中获取
下面会详细分析 CsrfGuard 类
**/
if (printConfig == null || "".equals(printConfig.trim())) {
printConfig = CsrfGuard.getInstance().isPrintConfig() ? "true" : null;
}
/** 如果printConfig 不为空,并且为 true 就打印日志
这里注意了,调用的不是 log4j这些,而是 context.log
这里打印的日志 在tomcat 目录下面配的 相应的地址
**/
if (printConfig != null && Boolean.parseBoolean(printConfig)) {
context.log(prefix
+ CsrfGuard.getInstance().toString());
}
}
2.2 CsrfGuard解析
上一节 里面涉及 CsrfGuard.getInstance().isPrintConfig() , 接下来主要 分析 CsrfGuard 类,注意一下 CsrfGuard .calss 这个类是 final 不可以继承 ,主要先从 config() 这个方法开始.
首先 个人感觉 config() 方法里面的else if ( !configurationProvider.isCacheable()) 这段逻辑 没有啥用
- 首先 已经放在缓存里面了, 缓存是1分钟 , 每1分钟就 刷新一次
- PropertiesConfigurationProvider 里面 isCacheable 里面 是 需要先初始化, 但是 已经能 从 cache 里面获取到, 说明肯定已经初始化了.
- 除了以上两种,那就是 自定义的 实现 ConfigurationProvider 的接口了, 只有这种可能了。
2.2.1 config()
private static ExpirableCache<Boolean, ConfigurationProvider> configurationProviderExpirableCache = new ExpirableCache<Boolean, ConfigurationProvider>(1);
private ConfigurationProvider config() {
// 判断属性是否为空, 如果为null, 返回 NullConfigurationProvider 实例, NullConfigurationProvider从名字可以看出,里面没有具体实现
// 这里在上面 contextInitialized() 方法里面已经初始化 赋值
if (this.properties == null) {
return new NullConfigurationProvider();
}
// 从缓存获取,这里设置的 缓存时间为 1分钟
ConfigurationProvider configurationProvider = configurationProviderExpirableCache.get(Boolean.TRUE);
// 这里是一个 double check , new 一个新对象
if (configurationProvider == null) {
synchronized (CsrfGuard.class) {
if (configurationProvider == null) {
// new 一个新对象,并放入缓存
configurationProvider = retrieveNewConfig();
}
}
} else if ( !configurationProvider.isCacheable()) {
//dont synchronize if not cacheable
configurationProvider = retrieveNewConfig();
}
return configurationProvider;
}
2.2.2 retrieveNewConfig()
private ConfigurationProvider retrieveNewConfig() {
ConfigurationProvider configurationProvider = null;
// 从配置文件获取对应的factory, 看需要哪种类型的factory 进行创建加载config
String configurationProviderFactoryClassName = this.properties.getProperty(
"org.owasp.csrfguard.configuration.provider.factory", PropertiesConfigurationProvider.class.getName());
// 加载对应的 类
Class<ConfigurationProviderFactory> configurationProviderFactoryClass = CsrfGuardUtils.forName(configurationProviderFactoryClassName);
ConfigurationProviderFactory configurationProviderFactory = CsrfGuardUtils.newInstance(configurationProviderFactoryClass);
// 进行创建并 放入缓存
configurationProvider = configurationProviderFactory.retrieveConfiguration(this.properties);
configurationProviderExpirableCache.put(Boolean.TRUE, configurationProvider);
return configurationProvider;
}
2.3 ConfigurationProvider 解析
接口 ConfigurationProvider 只要就是 提供各类属性对应的配置值, 只要有两个实现类, NullConfigurationProvider就是一个空实现, 这里就不分析了, 主要分析PropertiesConfigurationProvider
PropertiesConfigurationProvider 这里 主要就是属性的赋值,不再做具体的分析了, 里面有一个是需要替换一下 “%servletContext%”,
public static String commonSubstitutions(String input) {
if (input == null || !input.contains("%")) {
return input;
}
input = input.replace("%servletContext%", CsrfGuardUtils.defaultString(CsrfGuardServletContextListener.getServletContext()));
return input;
}
此外还有一段 对 action, unprotected pages,protected methods 这样的 处理,主要代码如下:
public PropertiesConfigurationProvider(Properties properties) {
try {
. . . . . . . . . .
. . . . . . . . . .
/**
第一步:实例化 action
**/
Map<String, IAction> actionsMap = new HashMap<String, IAction>();
// 遍历所有的属性
for (Object obj : properties.keySet()) {
String key = (String) obj;
//找到以 org.owasp.csrfguard.action. 开头的
if (key.startsWith(ACTION_PREFIX)) {
// 截取org.owasp.csrfguard.action. 之后的 字符串
String directive = key.substring(ACTION_PREFIX.length());
// 截取之后的 字符串 是否包含.
int index = directive.indexOf('.');
/** action name/class
如果不包含, 说明就是类名
获取对应的 具体 类的 路径 再 class.forName进行加载, 然后实例化
**/
if (index < 0) {
// 获取对应的 具体 类的 路径 再 class.forName进行加载, 然后实例化
String actionClass = propertyString(properties, key);
IAction action = (IAction) Class.forName(actionClass).newInstance();
// 设置name
action.setName(directive);
//放入map
actionsMap.put(action.getName(), action);
// 加到list
actions.add(action);
}
}
}
/**
第二步: 对action 参数进行初始化
这里没有和上一步合在一起,就是防止 在 遍历的时候,可能 先遍历到 参数,后遍历到实例
**/
for (Object obj : properties.keySet()) {
String key = (String) obj;
if (key.startsWith(ACTION_PREFIX)) {
String directive = key.substring(ACTION_PREFIX.length());
int index = directive.indexOf('.');
/** action name/class
index 大于0 ,说明是参数, "." 前面是 action 名称, 后面是参数
这里注意一下,只能两层(就是actionName.param 这种类型, 不支持参数里面嵌套参数)
**/
if (index >= 0) {
// 获取名称
String actionName = directive.substring(0, index);
// 从上面 获取对应的action 类
IAction action = actionsMap.get(actionName);
// 获取不到,报错
if (action == null) {
throw new IOException(String.format("action class %s has not yet been specified", actionName));
}
// 设置值, key->value
String parameterName = directive.substring(index + 1);
String parameterValue = propertyString(properties, key);
action.setParameter(parameterName, parameterValue);
}
}
}
/** 必须保证至少一个action 配置**/
if (actions.size() <= 0) {
throw new IOException("failure to define at least one action");
}
/** 初始化 保护和非保护页面
保护页面 是 以 org.owasp.csrfguard.protected. 为前缀, 将保护页面 放到 protectedPages里面
非保护页面 是 以 org.owasp.csrfguard.unprotected. 为前缀,将 非保护页面 放到 unprotectedPages 里面
**/
for (Object obj : properties.keySet()) {
String key = (String) obj;
if (key.startsWith(PROTECTED_PAGE_PREFIX)) {
String directive = key.substring(PROTECTED_PAGE_PREFIX.length());
int index = directive.indexOf('.');
/** page name/class **/
if (index < 0) {
String pageUri = propertyString(properties, key);
protectedPages.add(pageUri);
}
}
if (key.startsWith(UNPROTECTED_PAGE_PREFIX)) {
String directive = key.substring(UNPROTECTED_PAGE_PREFIX.length());
int index = directive.indexOf('.');
/** page name/class **/
if (index < 0) {
String pageUri = propertyString(properties, key);
unprotectedPages.add(pageUri);
}
}
}
/** 获取 需要保护的方法 ,一般是 POST,PUT,DELETE**/
String methodList = propertyString(properties, "org.owasp.csrfguard.ProtectedMethods");
if (methodList != null && methodList.trim().length() != 0) {
for (String method : methodList.split(",")) {
protectedMethods.add(method.trim());
}
}
/** 获取 不需要保护的方法类型 ,一般是GET ,或者不配做**/
methodList = propertyString(properties, "org.owasp.csrfguard.UnprotectedMethods");
if (methodList != null && methodList.trim().length() != 0) {
for (String method : methodList.split(",")) {
unprotectedMethods.add(method.trim());
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
三、ConfigurationProviderFactory 分析
接口 ConfigurationProviderFactory 主要是提供配置, 就一个 方法:
public ConfigurationProvider retrieveConfiguration(Properties properties); 返回 一个 ConfigurationProvider 类型的对象,ConfigurationProvider 也是一个接口, 具体的实现和细节在下一节 详细分析
ConfigurationProviderFactory 主要有 4个 实现类 , 接下来分析每一个具体的实现
- NullConfigurationProviderFactory
- PropertiesConfigurationProviderFactory
- ConfigurationOverlayProviderFactory
- ConfigurationAutodetectProviderFactory
3.1 NullConfigurationProviderFactory
从名字可以看出,这里什么都没有实现, 直接返回 NullConfigurationProvider 对象, 而 NullConfigurationProvider.class 什么都没有实现
private static ConfigurationProvider configurationProvider = null;
public ConfigurationProvider retrieveConfiguration(Properties properties) {
if (configurationProvider == null) {
configurationProvider = new NullConfigurationProvider();
}
return configurationProvider;
}
3.2 PropertiesConfigurationProviderFactory
这里是返回一个 PropertiesConfigurationProvider 对象 , PropertiesConfigurationProvider 后面详细分析
public ConfigurationProvider retrieveConfiguration(Properties properties) {
if (configurationProvider == null) {
try {
configurationProvider = new PropertiesConfigurationProvider(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return configurationProvider;
}
3.3 ConfigurationOverlayProviderFactory
这里是进行扩展属性的添加类
public ConfigurationProvider retrieveConfiguration(Properties originalProperties) {
// 获取对应的configurationOverlayProvider 对象
ConfigurationOverlayProvider configurationOverlayProvider = ConfigurationOverlayProvider.retrieveConfig();
// 获取属性,这块 具体逻辑在下面分析
Properties properties = configurationOverlayProvider.properties();
// 对属性的负责
return new PropertiesConfigurationProvider(properties);
}
3.3.1 ConfigurationOverlayProvider
注意 :ConfigurationOverlayProvider 这个类 没有实现 ConfigurationProvider ,继承了 ConfigPropertiesCascadeBase 这个 抽象类 , 主要方法,
public static ConfigurationOverlayProvider retrieveConfig() {
return retrieveConfig(ConfigurationOverlayProvider.class);
}
@Override
protected String getMainExampleConfigClasspath() {
// 这一段是对 mainExampleConfigClasspath 进行赋值
if (mainExampleConfigClasspath == null) {
//is the main config file there?
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(OWASP_CSRF_GUARD_PROPERTIES);
if (inputStream != null) {
mainExampleConfigClasspath = OWASP_CSRF_GUARD_PROPERTIES;
CsrfGuardUtils.closeQuietly(inputStream);
} else {
inputStream = getClass().getClassLoader().getResourceAsStream(META_INF_CSRFGUARD_PROPERTIES);
if (inputStream != null) {
mainExampleConfigClasspath = META_INF_CSRFGUARD_PROPERTIES;
CsrfGuardUtils.closeQuietly(inputStream);
} else {
//hmm, its not there, but use it anyways
mainExampleConfigClasspath = OWASP_CSRF_GUARD_PROPERTIES;
}
}
}
/** 这里返回
当你的 ConfigFileName 的路径配置不对,或者文件不存在是, 其实这是是加载的默认 CsrfGuardServletContextListener.getConfigFileName() dou
**/
return ConfigPropertiesCascadeUtils.defaultIfBlank(CsrfGuardServletContextListener.getConfigFileName(),
mainExampleConfigClasspath);
}
3.3.2 ConfigPropertiesCascadeBase
ConfigPropertiesCascadeBase 是一个级联配置的抽象类,可以通过 继承这个类,进行自定义的一些扩展
// 定义缓存, 个人感觉直接初始化就可以了,没有必要在调用到的地方再进行 初始化
private static Map<Class<? extends ConfigPropertiesCascadeBase>, ConfigPropertiesCascadeBase> configSingletonFromClass = null;
protected static <T extends ConfigPropertiesCascadeBase> T retrieveConfig(Class<T> configClass) {
// 如果configSingletonFromClass 为null ,进行初始化
if (configSingletonFromClass == null) {
configSingletonFromClass =
new HashMap<Class<? extends ConfigPropertiesCascadeBase>, ConfigPropertiesCascadeBase>();
}
/**
获取 对应的 值,如果没有,直接 new 一个实例,并放入缓存
需要注意的是, 这个类 有没有 私有的构造函数, 如果有 私有的 构造器,那就 需要使用
Constructor.newInstance(), 这个可以反射任何构造器,以及私有构造器
而Class.newInstance() 为无参构造器,需要可见
**/
ConfigPropertiesCascadeBase configPropertiesCascadeBase = configSingletonFromClass.get(configClass);
if (configPropertiesCascadeBase == null) {
configPropertiesCascadeBase = ConfigPropertiesCascadeUtils.newInstance(configClass, true);
configSingletonFromClass.put(configClass, configPropertiesCascadeBase);
}
//调用retrieveFromConfigFileOrCache 方法 , 具体逻辑下方
return (T)configPropertiesCascadeBase.retrieveFromConfigFileOrCache();
}
3.3.2.1 retrieveFromConfigFileOrCache 分析
protected ConfigPropertiesCascadeBase retrieveFromConfigFileOrCache() {
Map<String, Object> debugMap = new LinkedHashMap<String, Object>();
try {
/**
判断缓存是否为null , 为null 则初始化,下面这种if (true) 这种写法 比较差, 需要摈弃 ,这里的代码太菜了
**/
if (configFileCache == null) {
if (true) {
debugMap.put("configFileCache", null);
}
configFileCache =
new HashMap<Class<? extends ConfigPropertiesCascadeBase>, ConfigPropertiesCascadeBase>();
}
// 获取对应的缓存
ConfigPropertiesCascadeBase configObject = configFileCache.get(this.getClass());
// 如果缓存为空
if (configObject == null) {
// 多余代码
if (true) {
}
// 打印 ovrlay properties file 的名字
if (true) {
debugMap.put("mainConfigClasspath", this.getMainConfigClasspath());
}
// 创建,并 放入缓存
configObject = retrieveFromConfigFiles();
configFileCache.put(this.getClass(), configObject);
} else {
// 存在,那就是 判断是否需要再次检查
if (configObject.needToCheckIfFilesNeedReloading()) {
if (true) {
debugMap.put("needToCheckIfFilesNeedReloading", true);
}
// 这里 又用了 double-check
synchronized (configObject) {
configObject = configFileCache.get(this.getClass());
//check again in case another thread did it
if (configObject.needToCheckIfFilesNeedReloading()) {
if (true) {
debugMap.put("needToCheckIfFilesNeedReloading2", true);
}
/** 这里就是检查 文件是否有变化, 这里 逻辑 需要优化,
每一个文件进行对比, 指导发现有异常,再下一步进行替换
如果没有差异,那也已经对每一个文件都 load 了一遍
为啥不直接替换,反正都是要 load 一遍
主要是这里运行下面的走配置的方法retrieveFromConfigFiles() ,可以动态添加文件,如果不添加文件,可以进行适当的优化
**/
if (configObject.filesNeedReloadingBasedOnContents()) {
if (true) {
debugMap.put("filesNeedReloadingBasedOnContents", true);
}
configObject = retrieveFromConfigFiles();
configFileCache.put(this.getClass(), configObject);
}
}
}
}
}
if (true) {
debugMap.put("configObjectPropertyCount", configObject == null ? null
: (configObject.properties() == null ? "propertiesNull" : configObject.properties().size()));
}
return configObject;
} finally {
// 打印log , 这里 的 iLogger 里面 竟然是null , 瞎写 ,不会运行到
// 这样要打印,也别放 finally 里面
ILogger iLogger = iLogger();
if (iLogger != null) {
iLogger.log(LogLevel.Debug, ConfigPropertiesCascadeUtils.mapToString(debugMap));
}
}
}
3.3.2.2 retrieveFromConfigFiles分析
这个方法 主要流程就是:
- 获取扩展配置 文件名称,并加载
- 从 扩展配置里面获取两个配置 ,获取获取不到,那就再从 指定的 一开始的文件里面获取
两个必须配置,不然报错( 层次结构的文件配置键,检查配置的时间键)第一个 值 就是 指定哪些文件, 第二个 就是 再次check 的时间间隔 - 对获取到的 层次结构的文件配置键 解析, 一般的都是 classpath:Owasp.CsrfGuard.properties , 然后加载里面的属性
protected ConfigPropertiesCascadeBase retrieveFromConfigFiles() {
// 加载 对应的覆盖文件
Properties mainConfigFile = propertiesFromResourceName(this.getMainConfigClasspath(), false);
String secondsToCheckConfigString = null;
String overrideFullConfig = null;
// 如果加载到对应的文件,获取到 层次结构值的配置键,检查配置的时间键
if (mainConfigFile != null) {
overrideFullConfig = mainConfigFile.getProperty(this.getHierarchyConfigKey());
secondsToCheckConfigString = mainConfigFile.getProperty(this.getSecondsToCheckConfigKey());
}
//如果 没有找到对应的值,那就从 默认的 例子文件Owasp.CsrfGuard.properties 里面获取,如果指定了 configFileName 那就
// 从指定的文件里面获取
if (ConfigPropertiesCascadeUtils.isBlank(overrideFullConfig) || ConfigPropertiesCascadeUtils.isBlank(secondsToCheckConfigString)) {
Properties mainExampleConfigFile = propertiesFromResourceName(this.getMainExampleConfigClasspath(), false);
// 再次获取对应的 值和 配置的check时间
if (mainExampleConfigFile != null) {
if (ConfigPropertiesCascadeUtils.isBlank(overrideFullConfig)) {
overrideFullConfig = mainExampleConfigFile.getProperty(this.getHierarchyConfigKey());
}
if (ConfigPropertiesCascadeUtils.isBlank(secondsToCheckConfigString)) {
secondsToCheckConfigString = mainExampleConfigFile.getProperty(this.getSecondsToCheckConfigKey());
}
}
}
//没有找到,报错
if (ConfigPropertiesCascadeUtils.isBlank(overrideFullConfig)) {
throw new RuntimeException("Cant find the hierarchy config key: " + this.getHierarchyConfigKey()
+ " in config files: " + this.getMainConfigClasspath()
+ " or " + this.getMainExampleConfigClasspath());
}
//没有找到,报错
if (ConfigPropertiesCascadeUtils.isBlank(secondsToCheckConfigString)) {
throw new RuntimeException("Cant find the seconds to check config key: " + this.getSecondsToCheckConfigKey()
+ " in config files: " + this.getMainConfigClasspath()
+ " or " + this.getMainExampleConfigClasspath());
}
//实例化一个当前类
ConfigPropertiesCascadeBase result = ConfigPropertiesCascadeUtils.newInstance(this.getClass(), true);
// 类型转换
try {
result.timeToCheckConfigSeconds = ConfigPropertiesCascadeUtils.intValue(secondsToCheckConfigString);
} catch (Exception e) {
throw new RuntimeException("Invalid integer seconds to check config config value: " + secondsToCheckConfigString
+ ", key: " + this.getSecondsToCheckConfigKey()
+ " in config files: " + this.getMainConfigClasspath()
+ " or " + this.getMainExampleConfigClasspath());
}
// String 转list
List<String> overrideConfigStringList = ConfigPropertiesCascadeUtils.splitTrimToList(overrideFullConfig, ",");
result.configFiles = new ArrayList<ConfigFile>();
for (String overrideConfigString : overrideConfigStringList) {
/**
overrideConfigString 一般都是类似这种 格式 classpath:Owasp.CsrfGuard.properties
这里是new 出一个新的ConfigFile , 里面有两个参数比较重要
一个是configFileType ,这个是枚举类型, 一般是FILE / CLASSPATH ,对应 overrideConfigString :的前缀
另外一个是 configFileTypeConfig , 这个对应 overrideConfigString :的后面部分
**/
ConfigFile configFile = new ConfigFile(overrideConfigString);
添加 到 configFiles 列表
result.configFiles.add(configFile);
//lets append the properties
//InputStream inputStream = configFile.getConfigFileType().inputStream(configFile.getConfigFileTypeConfig(), this);
// 这里就是追加 指定文件的 属性, 调用 枚举类里面的 抽象类 inputStream 方法 , 返回形式是 String 类型
try {
//get the string and store it first (to see if it changes later)
String configFileContents = configFile.retrieveContents(this);
configFile.setContents(configFileContents);
result.properties.load(new StringReader(configFileContents));
} catch (Exception e) {
throw new RuntimeException("Problem loading properties: " + overrideConfigString, e);
}
}
return result;
}
3.4 ConfigurationAutodetectProviderFactory分析
ConfigurationAutodetectProviderFactory 这个 工厂类的 作用就是自动加载 额外名称固定的覆盖文件, 获取 具体的 检索工厂类,实例化 然后调用 具体的接口获取对应的configurationProvider, 保存到 缓存 ,2分钟的有效时间.
// 这里定义了一个 2分钟 过期的缓存
private static ExpirableCache<Boolean, ConfigurationProvider> configurationProviderCache = new ExpirableCache<Boolean, ConfigurationProvider>(2);
/**
* @see org.owasp.csrfguard.config.ConfigurationProviderFactory#retrieveConfiguration(Properties)
*/
public ConfigurationProvider retrieveConfiguration(Properties defaultProperties) {
// 从缓存获取
ConfigurationProvider configurationProvider = configurationProviderCache.get(Boolean.TRUE);
// 没有获取到,那就 新建,double-check
if (configurationProvider == null) {
synchronized (ConfigurationAutodetectProviderFactory.class) {
if (configurationProvider == null) {
Class<? extends ConfigurationProviderFactory> factoryClass = null;
// 从当前的 classes 目录下面开始 加载 Owasp.CsrfGuard.overlay.properties 文件, 这里 的文件名已经固定
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(ConfigurationOverlayProvider.OWASP_CSRF_GUARD_OVERLAY_PROPERTIES);
if (inputStream != null) {
// 进行load
Properties theProperties = new Properties();
try {
theProperties.load(inputStream);
} catch (IOException ioe) {
throw new RuntimeException("Error reading config file: " + ConfigurationOverlayProvider.OWASP_CSRF_GUARD_OVERLAY_PROPERTIES, ioe);
}
CsrfGuardUtils.closeQuietly(inputStream);
// 从 Owasp.CsrfGuard.overlay.properties 文件中获取对应的 factory的类名
String factoryClassName = theProperties.getProperty("org.owasp.csrfguard.configuration.provider.factory");
// 如果类名 不为空,并且不是空字符串
if (factoryClassName != null && !"".equals(factoryClassName)) {
/** 如果 指定的 类工厂 是 ConfigurationAutodetectProviderFactory ,那就报错
这里会 陷入无限循环的过程, 所以 ConfigurationAutodetectProviderFactory 这个 工厂类的 作用就是
自动加载 额外名称固定的覆盖文件, 获取 具体的 检索工厂类,实例化 然后调用 具体的接口获取对应的
configurationProvider, 保存到 缓存 ,2分钟的有效时间.
**/
if (ConfigurationAutodetectProviderFactory.class.getName().equals(factoryClassName)) {
throw new RuntimeException("Cannot specify auto detect factory in override file (recursion), pick the actual factory: " + factoryClassName);
}
// 通过class.forName 进行加载
factoryClass = CsrfGuardUtils.forName(factoryClassName);
}
}
// 如果 factoryClass 为null , 这里重新指定为 PropertiesConfigurationProviderFactory 进行 检索文件
if (factoryClass == null) {
factoryClass = PropertiesConfigurationProviderFactory.class;
}
/** 进行实例化,并调用 PropertiesConfigurationProviderFactory 的 retrieveConfiguration 方法进行检索文件
并 存入缓存, 2分钟过期
**/
ConfigurationProviderFactory factory = CsrfGuardUtils.newInstance(factoryClass);
configurationProvider = factory.retrieveConfiguration(defaultProperties);
configurationProviderCache.put(Boolean.TRUE, configurationProvider);
}
}
}
return configurationProvider;
}
四、小结
本章 从 web.xml 里面的 listener 加载开始, 对涉及到的 配置加载和 加载机制 等做了分析,对涉及到的 类、方法都 进行了解析.