作者:尹吉欢
来源:猿天地
一些比较重要的配置信息,比如密码之类的敏感配置,我们希望将配置加密存储,保证安全性。Apollo框架本身没有提供数据加密的功能,如果想要实现数据加密的功能有两种方式,第一种是改Apollo的源码,增加加解密的逻辑,第二种比较简单,基于第三方的框架来对数据进行解密。
jasypt-spring-boot是一个基于Spring Boot开发的框架,可以将properties中加密的内容自动解密,在Apollo中也可以借助于jasypt-spring-boot这个框架来实现数据的加解密操作。
jasypt-spring-boot GitHub地址:https://github.com/ulisesbocchio/jasypt-spring-boot
将我们需要加密的配置通过jasypt-spring-boot提供的方法进行加密,然后将加密的内容配置在Apollo中,当项目启动的时候,jasypt-spring-boot会将Apollo加密的配置进行解密,从而让使用者获取到解密之后的内容。
创建一个新的Maven项目,加入Apollo和jasypt的依赖:
<dependency>
<groupId>com.ctrip.framework.apollo</groupId>
<artifactId>apollo-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>1.16</version>
</dependency>
加入下面的依赖信息:
server.port=8081
app.id=SampleApp
apollo.meta=http://localhost:8080
apollo.bootstrap.enabled=true
apollo.bootstrap.namespaces=application
jasypt.encryptor.password=yinjihaunkey
jasypt.encryptor.password:配置加密的Key
创建一个加密的工具类,用于加密配置:
public class EncryptUtil {
/**
* 制表符、空格、换行符 PATTERN
*/
private static Pattern BLANK_PATTERN = Pattern.compile("\\s*|\t|\r|\n");
/**
* 加密Key
*/
private static String PASSWORD = "yinjihaunkey";
/**
* 加密算法
*/
private static String ALGORITHM = "PBEWithMD5AndDES";
public static Map<String, String> getEncryptedParams(String input) {
//输出流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(1024);
PrintStream cacheStream = new PrintStream(byteArrayOutputStream);
//更换数据输出位置
System.setOut(cacheStream);
//加密参数组装
String[] args = {"input=" + input, "password=" + PASSWORD, "algorithm=" + ALGORITHM};
JasyptPBEStringEncryptionCLI.main(args);
//执行加密后的输出
String message = byteArrayOutputStream.toString();
String str = replaceBlank(message);
int index = str.lastIndexOf("-");
//返回加密后的数据
Map<String, String> result = new HashMap<String, String>();
result.put("input", str.substring(index + 1));
result.put("password", PASSWORD);
return result;
}
/**
* 替换制表符、空格、换行符
*
* @param str
* @return
*/
private static String replaceBlank(String str) {
String dest = "";
if (!StringUtils.isEmpty(str)) {
Matcher matcher = BLANK_PATTERN.matcher(str);
dest = matcher.replaceAll("");
}
return dest;
}
public static void main(String[] args) {
System.out.println(getEncryptedParams("hello"));
}
}
执行main方法,可以得到如下输出:
{input=0JK4mrGjPUxkB4XuqEv2YQ==, password=yinjihaunkey}
input就是hello加密之后的内容,将input的值复制存储到Apollo中,存储的格式需要按照一定的规则才行:
test.input = ENC(0JK4mrGjPUxkB4XuqEv2YQ==)
需要将加密的内容用ENC包起来,这样jasypt才会去解密这个值。
使用的地方可以直接根据名称注入配置,比如:
@Value("${test.input}")
private String input;
input的值就是解密之后的值,使用者不需要关心解密逻辑,jasypt框架在内部处理好了。
jasypt整合Apollo也是有一些不足的地方,目前我只发现了下面几个问题:
在配置中心修改值后,项目中的值不会刷新
注入Config对象获取的值无法解密
@ApolloConfig
private Config config;
@GetMapping("/config/getUserName3")
public String getUserName3() {
return config.getProperty("test.input", "yinjihuan");
}
上面列举的2个问题,跟jasypt的实现方式是有关系的,意味着这种加密的方式可能只适合数据库密码之类的,启动时是可以解密的,而且只是用一次,如果是某些比较核心的业务配置需要加密的话,jasypt是支持不了的,无法做到实时更新。下章节我会讲解如何修改Apollo的源码来解决这2个问题。
前面章节中给大家介绍了如何使用jasypt为Apollo中的配置进行加解密操作,基本的需求是能够实现的,但还是有一些不足的地方。
jasypt只是在启动的时候将Spring中带有ENC(xx)这种格式的配置进行解密,当配置发生修改时无法更新。由于Apollo框架本身没有这种对配置加解密的功能,如果我们想实现加解密,并且能够动态的更新,就需要对Apollo的源码做一些修改来满足需求。
对源码修改还需要重新打包,笔者在这边介绍一个比较简单的实现方式,就是创建一个跟Apollo框架中一模一样的类名进行覆盖,这样也不用替换已经在使用的客户端。
如果配置中心存储的内容是加密的,意味着Apollo客户端从配置中心拉取下来的配置也是加密之后的,我们需要在配置拉取下来之后就对配置进行解密,然后再走后面的流程,比如绑定到Spring中。在这个业务点进行切入之后,配置中心加密的内容就可以自动变成解密后的明文,对使用者透明。
通过分析Apollo的源码,笔者找到了一个最合适的切入点来做这件事情,这个类就是com.ctrip.framework.apollo.internals.DefaultConfig,DefaultConfig是Coonfig接口的实现类,配置的初始化和获取都会经过DefaultConfig的处理。
在DefaultConfig内部有一个更新配置的方法updateConfig,可以在这个方法中对加密的数据进行解密处理:
private void updateConfig(Properties newConfigProperties, ConfigSourceType sourceType) {
Set<Object> keys = newConfigProperties.keySet();
for (Object k : keys) {
String key = k.toString();
String value = newConfigProperties.getProperty(key);
// 加密Value
if (value.startsWith("ENC(") && value.endsWith(")")) {
logger.debug("加密Value {}", value);
// 解密然后重新赋值
try {
String decryptValue = AesEncryptUtils.aesDecrypt(value.substring(3, value.length()-1), DECRYPT_KEY);
newConfigProperties.setProperty(key, decryptValue);
} catch (Exception e) {
logger.error("加密配置解密失败", e);
}
}
}
m_configProperties.set(newConfigProperties);
m_sourceType = sourceType;
}
这边使用了AES来解密,也就是说配置中心的加密内容也需要用相同的加密算法进行加密,至于格式的话还是用的ENC(xx)这种格式来标识这就是一个加密的配置内容。解密之后将解密的明文内容重新赋值到Properties 中,其他的流程不变。
创建一个加密测试类,加密配置内容,复制存储到Apollo中
public class Test {
public static void main(String[] args) {
String msg = "hello yinjihaun";
try {
String encryptMsg = AesEncryptUtils.aesEncrypt(msg, "1111222233334444");
System.out.println(encryptMsg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出内容如下:
Ke4LIPGOp3jCwbIHtmhmBA==
存储到Apollo中需要用ENC将加密内容包起来,如下:
test.input = ENC(Ke4LIPGOp3jCwbIHtmhmBA==)
还是用之前的代码进行测试,Config获取和Spring注入的方式如可以成功的获取到解密的数据,并且在配置中心修改后也能实时推送到客户端成功解密。
本文摘自于《Spring Cloud微服务 入门 实战与进阶》一书。
推荐阅读
《Spring Cloud微服务:入门、实战与进阶》
推荐语:资深微服务技术专家从技术原理、工程实践、进阶提升3个维度详解Spring Cloud微服务架构与开发,快速实现从入门到进阶,掌握微服务典型场景解决方案。
你与世界
只差一个
公众号
点击“阅读原文“了解更多微服务好书!
文章浏览阅读1w次。在测试中遇到的一些问题elasticsearch日志中出现警告[WARN ][http.netty ] [Banshee] Caught exception while handling client http traffic, closing connection [id: 0x61615783, /127.0.0.1:37923 => /127.0.0._failed with org.apache.flink.shaded.akka.org.jboss.netty.handler.codec.frame
文章浏览阅读4.4k次。常在河边走,哪有不湿鞋。由于某款软件是收费的,需要下载绿色版,就在网上下载,作为老司机,每个界面还是很小心地、仔细的点击下一步,尽量不被迷惑了双眼,只下载所需要的软件。但没想到还是中招了,什么2345、好压王、高仿office和wps的一款(名字已忘记)、乱七八糟的输入法,头大,就一个个的删除掉,但没想到不仅仅是流氓软件偷偷下载,卸载那么简单,它还会偷偷的修改浏览器的主页,以及不知道的一些东西。会恶心你,可能这就是免费的代价。果真是天下没有免费的午餐。----------------------_火狐浏览器开启之后自动打开网页
文章浏览阅读428次。递归的用途:1)各种数学问题如:八皇后问题 , 汉诺塔, 阶乘问题, 迷宫问题, 球和篮子的问题(google编程大赛)2)各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等.3)将用栈解决的问题-->递归代码比较简洁递归需要遵守的重要规则:1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)2)方法的局部变量是独立的,不会相互影响, 比如n变量3)如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.4..._迷宫来回走
文章浏览阅读1.1k次,点赞4次,收藏4次。有时候为了表和表之间联系方便,通常会增加主键和外键可以这样理解,比方说有两个表,一个员工表,一个部门表员工表中有其表的主键id,员工名name部门表中有其表的主键depid,部门depart那么我在员工表中可以添加一个字段depid,使其成为部门表的外键其中外键只能关联主键那么可以设员工表中字段depid(并非主键)为其部门表的外键,关联的字段为部门表的主键depid(为主键)部门表..._店铺的主键
文章浏览阅读1.5k次,点赞2次,收藏7次。Qt之javascript(QJSEngine)c++调用js调用js函数,QJSValue::call() QJSValue fun = myEngine.evaluate("(function(a, b) { return a + b; })"); QJSValueList args; args << 1 << 2; QJSValue threeAgain = fun.call(args);执行脚本 QJSEngine::evalu_qt qjsengine
文章浏览阅读3.6k次。Oracle_字段拼接逗号单引号select code,USER_USERID,USER_USERNAME,('update t_user set userId = ''' || USER_USERID|| ''',userName=''' || USER_USERNAME|| ''' where userCode = ''' || code||'''')from user_in..._oracle拼接逗号引号
文章浏览阅读1.2w次,点赞99次,收藏187次。导语:并查集是一种精巧的算法,本身并不难理解,却很常用,在许多场景下都能找到并查集的身影。本文作者封承成,年仅12岁,非常感谢他的投稿。并查集是什么并查集,是一种判断“远房亲戚”的算法。..._并查集
文章浏览阅读653次。1.串的定义串是由零个或多个字符组成的有限序列。 (限定了元素为字符的线性表)空串:含有零个字符。子串:串(主串)中任意连续字符组成的子序列空格串:由一个或者多个空格组成的串(空格串不是空串)2.串的各种基本操作串的定义串的赋值..._串的合并,求子串有什么用
文章浏览阅读1.3k次。用Qt+ffmpeg写播放器很多人有疑问,为何不用Qt自己的多媒体框架来写,最重要的原因是Qt自带的目前都依赖具体的本地解码器,如果解码器不支持,那就是歇菜的,最多支持个MP4格式,而且在手机上也都是支持本地少部分格式的文件,对于各种视频流完全无能无力。而ffmpeg就不一样了,本身就是自带各种解码器,解码能力绝对超一流杠杠的,本身在桌面端就已经用Qt+ffmpeg写好了对应的播放器,手机版本只需要对应的ffmpeg的库替换成对应安卓版本(一堆.so库文件)就行。_qt 视频录像和ipc
文章浏览阅读445次。①查看selinux配置:②还需要注意的是改之前需要备份一下下,以用户名+时间的方式比较醒目较为推荐,防止误操作导致恢复不能:③利用sed命令将enforcing换成disabled(永久生效):检查一下下,是否替换成功:④a.getenforce:查看selinux状态b.setenforce:设置selin_selinux s1级别设置
文章浏览阅读505次。2428: [HAOI2006]均分数据Time Limit: 5 Sec Memory Limit: 128 MBSubmit: 2183 Solved: 680[Submit][Status][Discuss]Description已知N个正整数:A1、A2、……、An 。今要将它们分成M组,使得各组数据的数值和最平均,即各组的均方差最小。均方差公式如下:,其中_均分数据
文章浏览阅读5.6k次,点赞7次,收藏25次。[RaspeberryPi_3b+] 树莓派:Linux内核镜像、设备树的编译及替换一、开发环境、源码以及编译工具1. 开发环境的安装目前我是在 Ubuntu 16 的虚拟机环境下做树莓派相关的开发,以下命令用来安装开发必需的环境:sudo apt-get updatesudo apt-get install bc build-essential git unzipsudo apt-g..._树莓派 usb设备通讯 内核 linux