通过实现 PropertySourceLoader 接口,重写 Spring Boot 的 YAML 解析器 YamlPropertySourceLoader。步骤如下:
(1)自定义实现 PropertySourceLoader 接口
(2)配置解析器
我们的重点是对解析后的配置中加密字段配置进行解密,再设置回去。代码如下:
package com.hxstrive.demo.ext.YamlPropertySourceLoaderExt; import org.springframework.beans.factory.config.YamlProcessor; import org.springframework.boot.env.PropertySourceLoader; import org.springframework.boot.yaml.SpringProfileDocumentMatcher; import org.springframework.core.env.MapPropertySource; import org.springframework.core.env.PropertySource; import org.springframework.core.io.Resource; import org.springframework.util.ClassUtils; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.representer.Representer; import org.yaml.snakeyaml.resolver.Resolver; import java.io.IOException; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; /** * 对 Spring Boot 默认的 Yaml 解析器进行重写 * 对加密的配置项进行解密操作 * * @author hxstrive.com */ public class YamlPropertySourceLoaderExt implements PropertySourceLoader { /** DES 加密解密的密钥KEY */ private static final String DES_KEY_STR = "rVoO24XK^$s&VC^Gth2Dh9yWadIvsTofa*iJ4OzF_w62pDUNVt2w76u_RcGi7Vqp"; public YamlPropertySourceLoaderExt() {} @Override public String[] getFileExtensions() { return new String[]{"yml", "yaml"}; } @Override public PropertySource<?> load(String name, Resource resource, String profile) throws IOException { if (ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", (ClassLoader)null)) { YamlPropertySourceLoaderExt.Processor processor = new YamlPropertySourceLoaderExt.Processor(resource, profile); Map<String, Object> source = processor.process(); if (!source.isEmpty()) { return new MapPropertySource(name, source); } } return null; } private static class Processor extends YamlProcessor { Processor(Resource resource, String profile) { if (profile == null) { this.setMatchDefault(true); this.setDocumentMatchers(new DocumentMatcher[]{new SpringProfileDocumentMatcher()}); } else { this.setMatchDefault(false); this.setDocumentMatchers(new DocumentMatcher[]{new SpringProfileDocumentMatcher(new String[]{profile})}); } this.setResources(new Resource[]{resource}); } @Override protected Yaml createYaml() { return new Yaml(new StrictMapAppenderConstructor(), new Representer(), new DumperOptions(), new Resolver() { @Override public void addImplicitResolver(Tag tag, Pattern regexp, String first) { if (tag != Tag.TIMESTAMP) { super.addImplicitResolver(tag, regexp, first); } } }); } public Map<String, Object> process() { final Map<String, Object> result = new LinkedHashMap(); this.process(new MatchCallback() { @Override public void process(Properties properties, Map<String, Object> map) { // 关键点,遍历解析出来的 Map,对加密配置解密 decrypt(map); result.putAll(Processor.this.getFlattenedMap(map)); } }); return result; } private void decrypt(Map<String, Object> map) { if(null == map) { return; } for(Map.Entry<String,Object> entry : map.entrySet()) { Object value = entry.getValue(); if(value instanceof Map) { // 递归调用 decrypt((Map<String,Object>) value); } else if(value instanceof String) { // 开始解密 String valueStr = (String) value; // 解密配置格式为 ENC(密文) if(valueStr.matches("^ENC\\(.+\\)$")) { DESUtils desUtils = new DESUtils(); desUtils.setKey(DES_KEY_STR); // DES 解密操作 String val = desUtils.getDecrypt(valueStr.replaceAll("ENC\\s*\\(\\s*", "") .replaceAll("\\s*\\)", "")); map.put(entry.getKey(), val); } } } } } }
在 Spring Boot 项目的 resouces 目录下面创建 META-INF/spring.factories 文件,内容如下:
org.springframework.boot.env.PropertySourceLoader=\ com.hxstrive.demo.ext.YamlPropertySourceLoaderExt