OWASP(CsrfGuard)源码解析02----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 的主要逻辑就是 进行 加载 文件 ,

  1. 文件名称(名称可以包含具体路径) 可以在web.xml 的 里面进行配置 , key 为 Owasp.CsrfGuard.Config

  2. 配置的路径 有3种 ,第一种 在 classes 目录下面, 第二种在 context 下面 , 第三种 直接 配置绝对路径

  3. 如果 没有配置 名称,默认Owasp.CsrfGuard.properties , 进行全局搜索并 加载

  4. 如果还是没有找到, 那就获取 jar 包下面的 “META-INF/csrfguard.properties” 文件,默认配置

  5. 进行 日志打印 , 这里 也是 先从 获取配置是否需要打印, 如果没有配置 ,再次获取 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分钟 , 每1分钟就 刷新一次
  2. PropertiesConfigurationProvider 里面 isCacheable 里面 是 需要先初始化, 但是 已经能 从 cache 里面获取到, 说明肯定已经初始化了.
  3. 除了以上两种,那就是 自定义的 实现 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分析

这个方法 主要流程就是:

  1. 获取扩展配置 文件名称,并加载
  2. 从 扩展配置里面获取两个配置 ,获取获取不到,那就再从 指定的 一开始的文件里面获取
    两个必须配置,不然报错( 层次结构的文件配置键,检查配置的时间键)第一个 值 就是 指定哪些文件, 第二个 就是 再次check 的时间间隔
  3. 对获取到的 层次结构的文件配置键 解析, 一般的都是 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 加载开始, 对涉及到的 配置加载和 加载机制 等做了分析,对涉及到的 类、方法都 进行了解析.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直打铁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值