一个网站主机多少钱一年,网站链接分析工具,个体工商户可以网站建设吗,个人网页制作源代码格式目录 一、背景
二、思路
A) 普通方式
B) 适合bootstrap.properties方式
三、示例
A) 普通方式#xff08;连接Redis集群#xff09;
A) 普通方式#xff08;连接RocketMQ#xff09;
B) 适合bootstrap.properties方式
四、总结 一、背景 SpringBoot和Sprin…目录 一、背景
二、思路
A) 普通方式
B) 适合bootstrap.properties方式
三、示例
A) 普通方式连接Redis集群
A) 普通方式连接RocketMQ
B) 适合bootstrap.properties方式
四、总结 一、背景 SpringBoot和SpringCloud中涉及多个配置文件配置文件中对于密码默认是明文方式这种方式在生产环境一般是不被允许的。为避免配置文件中出现明文应当在配置文件中配置为密文然后在启动时在程序内部完成解密。 本文提供了通用的处理方式可以适配以下几类配置文件
本地bootstrap.properties 在Spring的Bean创建之前的配置本地application.properties 在Spring的配置包括带profile环境的配置配置中心上的配置例如nacos上的Data ID 为了适应配置文件涉及密码由明文改为密文需要分为两步
①将配置文件中涉及密文的配置项配置为密文字符串需自己加密计算得到
②在Spring启动中读取密文字符串并解密还原。
二、思路 对于以上第②步Spring启动时的处理由于以上配置文件在Spring加载的时机和生命周期不同有两种处理方式
A) 普通方式 由于Spring中的对本地application.properties或者配置中心上的配置例如nacos上的Data ID在Spring Bean创建过程中会有对应的配置Bean通过注解Configuration申明的Java类Spring会自动根据读取解析配置文件并赋值给Bean。 因此若需要对密文字符串并解密还原可以对配置Bean通过注解Configuration申明的Java类进行继承Override重写对应的set方法完成解密。
B) 适合bootstrap.properties方式 对于Spring Cloud在bootstrap阶段还未创建Bean所以以上Override重写对应的set方法并不适用。所以对于bootstrap.properties配置文件。可通过实现EnvironmentPostProcessor接口来捕获Environment配置解密后将配置新值设置到Environment中。
三、示例
A) 普通方式连接Redis集群 下面以连接Redis集群为例进行说明连接Redis集群的配置项可以在本地application.properties或者配置中心上的配置例如nacos上的Data ID且其中spring.redis.password配置项值已经设置为密文。 下面代码对配置Bean通过注解Configuration申明的Java类RedisProperties进行继承Override重写对应的set方法。Java代码如下
package 包指定忽略请自定;import 忽略解密计算工具类SystemSecurityAlgorithm请自定;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.util.StringUtils;/*** 连接Redis集群的配置类【通过Configuration覆盖原Bean机制】:* 1、连接Redis的连接password不得出现明文故需在properties配置文件中配置为加密密文加密算法Java类为SystemSecurityAlgorithm然后在启动时通过本类解密* 2、贵金属应用服务采用多数据中心DataCenter部署。而每逻辑中心均有独立的Redis集群。 应用服务应连接同逻辑中心内的Redis集群既北京的应用服务不应该连接合肥Redis集群* 既对于同服务的不同实例应根据服务实例所在逻辑中心具体见枚举ServiceConstant.DataCenter定义的逻辑中心连接相同逻辑中心下的Redis集群。* 因此* a).以Spring标准Redis连接配置为基础对nodes值中各个IP端口配置在各IP前增加一个大写字母该IP所在DataCenter数据中心的英文代码* b).以Spring标准Redis连接配置为基础对password值改为可配多个密码以逗号分隔每个密码前增加一个大写字母该密码是连接哪个Redis集群的DataCenter数据中心的英文代码* 为支持以上定制化开发本类实现处理最终还原至Spring标准连接Redis的配置以供lettuce创建连接池。* -----------------------------------------------------------* 机制适用性* 除了通过Configuration覆盖原Bean机制还有通过实现EnvironmentPostProcessor接口机制。两种机制适用性说明如下* bootstrap.properties配置文件bootstrap阶段还未创建Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】* 本地application.properties配置文件正常SpringBoot启动通过Configuration注解的Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过Configuration覆盖原Bean机制】均可* 从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过Configuration覆盖原Bean机制】**/
Configuration
Primary // 由于默认RedisProperties作为配置类会自动创建Bean。 为避免存在两个同类型RedisPropertiesBean所以本类通过注解Primary使得只有本类生效。相当于替代默认RedisProperties
public class GjsRedisProperties extends RedisProperties {private static final org.slf4j.Logger log org.slf4j.LoggerFactory.getLogger(GjsRedisProperties.class);Overridepublic void setPassword(String orginPassword) {if(StringUtils.hasText(orginPassword)) {// 对密文解密并设置if (StringUtils.hasText(orginPassword) orginPassword.length() 32 ) { // 如果满足密码密文的长度及大小写要求视为密文解密String padStr SystemSecurityAlgorithm.decryptStr(orginPassword);log.debug(连接Redis配置项spring.redis.password: 解密前orginPassword[{}], 解密后padStr[{}], orginPassword, padStr); //为避免密码泄露仅debug才输出明文log.info(连接Redis配置项spring.redis.password: 对密文orginPassword[{}]已完成解密, orginPassword);super.setPassword(padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变log.warn(连接Redis配置项spring.redis.password的:orginPassword[{}]不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变, orginPassword);super.setPassword(orginPassword);}}}
}
A) 普通方式连接RocketMQ 下面以连接RocketMQ为例进行说明连接RocketMQ的配置项可以在本地application.properties或者配置中心上的配置例如nacos上的Data ID且其中rocketmq.producer.secret-key和rocketmq.consumer.secret-key配置项值已经设置为密文。 下面代码对配置Bean通过注解Configuration申明的Java类RocketMQProperties进行继承Override重写对应的set方法。Java代码如下
package 包指定忽略请自定;import 忽略解密计算工具类SystemSecurityAlgorithm请自定;
import org.apache.rocketmq.spring.autoconfigure.RocketMQProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;/*** 连接RocketMQ的配置类【通过Configuration覆盖原Bean机制】:* 因连接RocketMQ的secret-key不得出现明文故需在properties配置文件中配置为加密密文加密算法Java类为SystemSecurityAlgorithm然后在启动时通过本类解密* -----------------------------------------------------------* 机制适用性* 除了通过Configuration覆盖原Bean机制还有通过实现EnvironmentPostProcessor接口机制。两种机制适用性说明如下* bootstrap.properties配置文件bootstrap阶段还未创建Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】* 本地application.properties配置文件正常SpringBoot启动通过Configuration注解的Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过Configuration覆盖原Bean机制】均可* 从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过Configuration覆盖原Bean机制】**/
Configuration
Primary // 由于默认RocketMQProperties作为配置类会自动创建Bean。 为避免存在两个同类型RocketMQPropertiesBean所以本类通过注解Primary使得只有本类生效。相当于替代默认RocketMQProperties
public class GjsRocketMQProperties extends RocketMQProperties {final private String KEYNAME_PRODUCER_SECRET rocketmq.producer.secret-key;final private String KEYNAME_CONSUMER_SECRET rocketmq.consumer.secret-key;AutowiredConfigurableApplicationContext springContext;private static final org.slf4j.Logger log org.slf4j.LoggerFactory.getLogger(GjsRocketMQProperties.class);Overridepublic void setProducer(Producer producer) {final String orginSecretKey producer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) orginSecretKey.length() 32) { // 如果满足密码密文的长度及大小写要求视为密文解密String padStr SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug(连接RocketMQ配置项{}: 解密前orginSecretKey[{}], 解密后padStr[{}], KEYNAME_PRODUCER_SECRET, orginSecretKey, padStr); //为避免密码泄露仅debug才输出明文log.info(连接RocketMQ配置项{}: 对密文orginSecretKey[{}]已完成解密, KEYNAME_PRODUCER_SECRET, orginSecretKey);producer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中会从Spring的Environment中获取配置。// 附调用关系简要说明如下// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()// org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()// org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()// ......// org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_PRODUCER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变log.warn(连接RocketMQ配置项rocketmq.producer.secret-key值[{}]不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变, orginSecretKey);}super.setProducer(producer);}Overridepublic void setConsumer(PushConsumer pushConsumer) {final String orginSecretKey pushConsumer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) orginSecretKey.length() 32 ) { // 如果满足密码密文的长度及大小写要求视为密文解密String padStr SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug(连接RocketMQ配置项{}: 解密前orginSecretKey[{}], 解密后padStr[{}], KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //为避免密码泄露仅debug才输出明文log.info(连接RocketMQ配置项{}: 对密文orginSecretKey[{}]已完成解密, KEYNAME_CONSUMER_SECRET, orginSecretKey);pushConsumer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中会从Spring的Environment中获取配置。// 附调用关系简要说明如下// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()// org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()// org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()// ......// org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变log.warn(连接RocketMQ配置项{}的值[{}]不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变, KEYNAME_CONSUMER_SECRET, orginSecretKey);}super.setConsumer(pushConsumer);}Overridepublic void setPullConsumer(PullConsumer pullConsumer) {final String orginSecretKey pullConsumer.getSecretKey();// 对密文解密并设置if (StringUtils.hasText(orginSecretKey) orginSecretKey.length() 32 ) { // 如果满足密码密文的长度及大小写要求视为密文解密String padStr SystemSecurityAlgorithm.decryptStr(orginSecretKey);log.debug(连接RocketMQ配置项{}: 解密前orginSecretKey[{}], 解密后padStr[{}], KEYNAME_CONSUMER_SECRET, orginSecretKey, padStr); //为避免密码泄露仅debug才输出明文log.info(连接RocketMQ配置项{}: 对密文orginSecretKey[{}]已完成解密, KEYNAME_CONSUMER_SECRET, orginSecretKey);pullConsumer.setSecretKey(padStr);// 由于RocketMQ在构建DefaultRocketMQListenerContainer过程中会从Spring的Environment中获取配置。// 附调用关系简要说明如下// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.afterPropertiesSet()// org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer.initRocketMQPushConsumer()// org.apache.rocketmq.spring.support.RocketMQUtil.getRPCHookByAkSk()// org.springframework.core.env.AbstractEnvironment.resolveRequiredPlaceholders()// ......// org.springframework.boot.context.properties.source.ConfigurationPropertySourcesPropertyResolver.findPropertyValue()// 因此一并修改环境中的值使其能取得新值modifyEnvironmentValue(springContext.getEnvironment(), KEYNAME_CONSUMER_SECRET, padStr);} else { // 不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变log.warn(连接RocketMQ配置项{}的值[{}]不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变, KEYNAME_CONSUMER_SECRET, orginSecretKey);}super.setPullConsumer(pullConsumer);}/*** 对Spring的Environment的配置项的值修改为新值* param environment Spring的Environment对象* param keyName 配置项名* param newValue 新值*/private void modifyEnvironmentValue(ConfigurableEnvironment environment, final String keyName, String newValue) {if(!environment.containsProperty(keyName)) {log.warn(当前Spring的environment中不存在名为{}的配置项, keyName);return;}if(environment.getProperty(keyName, ).equals(newValue)) {log.debug(当前Spring的environment中配置项{}的值已与新值相同无需修改, keyName);return;}MapString, Object map new HashMap(); //用于存放新值map.put(keyName, newValue);// 若有map有值则把该map作为PropertySource加入列表中以实现把environment中对应key的value覆盖为新值// 必须加到First并且不能存在两个相同的Name的MapPropertySource值覆盖才能生效environment.getPropertySources().addFirst(new MapPropertySource(modifyEnvironmentValue-keyName, map));log.info(已对Spring的Environment的配置项{}的值修改为新值, keyName);}
}B) 适合bootstrap.properties方式 下面以连接Nacos配置中心为例进行说明需要在本地bootstrap.properties配置文件中指定连接Nacos配置中心的Nacos用户名、密码、服务端地址、Data ID等信息。bootstrap.properties配置文件有关连接Nacos配置中心类似如下
#Nacos配置中心及注册中心的authenticate鉴权用户名和密码需Nacos服务端开启auth鉴权
spring.cloud.nacos.usernamenacos
spring.cloud.nacos.password760dee29f9fc82af0cc1d6074879dc39
#Nacos配置中心服务端的地址和端口(形式ip:port,ip:port,...) 。注nacos-client1.x会按顺序选其中地址进行连接(前个连接失败则自动选后一个)。nacos-client2.x会随机选其中地址进行连接(若连接失败则自动另选)
spring.cloud.nacos.config.server-addrip1:8848,ip2:8848,ip3:8848,ip4:8848#Data ID的前缀如果不设置则默认取 ${spring.application.name}
#spring.cloud.nacos.config.prefix
#默认指定为开发环境
#spring.profiles.active
#Nacos命名空间此处不设置保持默认
#spring.cloud.nacos.config.namespace
#配置组如果不设置则默认为DEFAULT_GROUP
spring.cloud.nacos.config.groupG_CONFIG_GJS_SERVICE
#指定文件后缀如果不设置则默认为properties
spring.cloud.nacos.config.file-extensionproperties#以下为全局Data ID
spring.cloud.nacos.config.shared-configs[0].data-idNacosRegDiscoveryInfo.properties
spring.cloud.nacos.config.shared-configs[0].groupG_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[0].refreshtruespring.cloud.nacos.config.shared-configs[1].data-idXXXXX.properties
spring.cloud.nacos.config.shared-configs[1].groupG_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[1].refreshtruespring.cloud.nacos.config.shared-configs[2].data-idYYYYY.properties
spring.cloud.nacos.config.shared-configs[2].groupG_CONFIG_GJS_GLOBALSHARED
spring.cloud.nacos.config.shared-configs[2].refreshtrue
其中spring.cloud.nacos.password配置项值已经设置为密文。 下面的代码通过实现EnvironmentPostProcessor接口来捕获配置并将配置新值设置到Environment中。Java代码如下
package 包指定忽略请自定;import 忽略解密计算工具类SystemSecurityAlgorithm请自定;
import org.apache.commons.logging.Log;
import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.logging.DeferredLogFactory;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.util.StringUtils;import java.util.HashMap;
import java.util.Map;/*** 本类通过实现EnvironmentPostProcessor接口实现在Spring启动过程中从environment中读取指定的key值处理后然后把environment中对应key的value覆盖为新值。* 通过本类已经实现对bootstrap阶段的配置文件处理* 因连接Nacos的password不得出现明文故bootstrap配置文件中为加密密文加密算法Java类为SystemSecurityAlgorithm然后在启动时通过本类解密* -----------------------------------------------------------* 注意* a) 需要在META-INF下的spring.factories文件中配置本类后本类才会生效(才被Spring扫描识别到)* b) 因为本类是通过实现EnvironmentPostProcessor接口方式所以本类在SpringCloud启动过程中会被调用两次* 首先是在bootstrap配置文件加载后SpringCloud为支持配置中心的bootstrap阶段* 其次是在application配置文件加载后SpringBoot的正常启动时加载配置文件阶段* 机制适用性* 除了通过实现EnvironmentPostProcessor接口机制还有通过Configuration覆盖原Bean机制。两种机制适用性说明如下* bootstrap.properties配置文件bootstrap阶段还未创建Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】* 本地application.properties配置文件正常SpringBoot启动通过Configuration注解的Bean →→适合→→ 【实现EnvironmentPostProcessor接口机制】和【通过Configuration覆盖原Bean机制】均可* 从Nacos等配置中心获取得到的配置文件 →→适合→→ 【通过Configuration覆盖原Bean机制】**/
public class GjsEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {/*** The default order for the processor. 值越小优先级越高* 因bootstrap配置文件是通过{link org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor}完成加载处理* 由于本EnvironmentPostProcessor类需等待SpringCloud对bootstrap配置文件后才能执行所以本EnvironmentPostProcessor类优先级需更低*/public static final int ORDER Ordered.HIGHEST_PRECEDENCE 50;private final DeferredLogFactory logFactory;private final Log logger;public GjsEnvironmentPostProcessor(DeferredLogFactory logFactory,ConfigurableBootstrapContext bootstrapContext) {this.logFactory logFactory;this.logger logFactory.getLog(getClass());}Overridepublic int getOrder() {return ORDER;}/*** 从environment中读取指定的key并进行解密解密后的结果放入map对象中* param environment 已经有的Spring环境* param keyName 指定的key名* param map 若完成解密则将解密后的结果放入map对象*/private void decodePwd(ConfigurableEnvironment environment, String keyName, MapString, Object map ) {if(!environment.containsProperty(keyName)) {this.logger.debug(EnvironmentPostProcessor 当前Spring的environment中不存在名为keyName的配置项);return;}final String origalValue environment.getProperty(keyName);// 对密文解密并设置if (StringUtils.hasText(origalValue) origalValue.length() 32) { // 如果满足密码密文的长度及大小写要求视为密文解密String padStr SystemSecurityAlgorithm.decryptStr(origalValue);this.logger.debug(EnvironmentPostProcessor 配置项keyName原值[origalValue], 解密后值[padStr]); //为避免在日志中密码泄露仅debug才输出明文this.logger.info(EnvironmentPostProcessor 配置项keyName原值[origalValue]已完成解密);map.put(keyName, padStr);}else {this.logger.warn(EnvironmentPostProcessor 配置项keyName值[origalValue]不满足密码密文的长度及大小写要求(视为明文,不解密)保持不变);}}Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {this.logger.debug(EnvironmentPostProcessor before PropertySources size environment.getPropertySources().size());this.logger.debug(EnvironmentPostProcessor before PropertySources : environment.getPropertySources());MapString, Object map new HashMap(); //用于存放新值decodePwd(environment, spring.cloud.nacos.password, map);if(!map.isEmpty()) {// 若有map有值则把该map作为PropertySource加入列表中以实现把environment中对应key的value覆盖为新值// 必须加到First并且不能存在两个相同的Name的MapPropertySource值覆盖才能生效environment.getPropertySources().addFirst(new MapPropertySource(afterDecodePassword, map));}this.logger.debug(EnvironmentPostProcessor after PropertySources size environment.getPropertySources().size());this.logger.debug(EnvironmentPostProcessor after PropertySources : environment.getPropertySources());}}四、总结 通过以上两种方式可解决Spring各类配置文件对配置密文的适配和处理。 同时不仅仅用于密文凡是需对配置文件的内容在启动时进行改变情况都可以按以上方式进行处理。例如启动时对配置项值中多个IP进行动态使用等情形。