简介
- nacos作为一款优秀的注册发现中心和配置管理工具,能够实现微服务配置的热更新,同时从代码中解耦出去,更加自由的控制服务的上线和下线,使所有的操作全部可视化,独立化。
- nacos官网
- 但是在nacos原生版本中,nacos持久化连接mysql数据库是通过配置文件读取数据库的ip、port、user及password的。
这其实是一种不安全的操作,如果数据库权限约束做的不好,那么数据库密码泄露将是一件很严重的事情
修改源码
- 完成个性化定制,自定义数据库密码的加密方式,其实并不难,只需要修改nacos源码中数据库连接部分即可
- nacos(1.4.1版本)数据库连接初始化所在的类是com.alibaba.nacos.config.service.DataSource包下的ExternalDataSourceProperties类,完成数据库信息装配的方法是 build 方法
- 我们需要做的只是在nacos读取到配置文件中的密码之后,并且将属性值赋值到实体类之前将密码解析出来即可,以下是源码以及修改的部分
List<HikariDataSource> build(Environment environment, Callback<HikariDataSource> callback) {
List<HikariDataSource> dataSources = new ArrayList<>();
Binder.get(environment).bind("db", Bindable.ofInstance(this));
Preconditions.checkArgument(Objects.nonNull(num), "db.num is null");
Preconditions.checkArgument(CollectionUtils.isNotEmpty(user), "db.user or db.user.[index] is null");
Preconditions.checkArgument(CollectionUtils.isNotEmpty(password), "db.password or db.password.[index] is null");
for (int index = 0; index < num; index++) {
int currentSize = index + 1;
Preconditions.checkArgument(url.size() >= currentSize, "db.url.%s is null", index);
DataSourcePoolProperties poolProperties = DataSourcePoolProperties.build(environment);
poolProperties.setDriverClassName(JDBC_DRIVER_NAME);
poolProperties.setJdbcUrl(url.get(index).trim());
poolProperties.setUsername(getOrDefault(user, index, user.get(0)).trim());
/* nacos在这里读取数据库密码 */
//---------------------------------------------------------------------------------------
/*poolProperties.setPassword(getOrDefault(password, index, password.get(0)).trim());*/
// 在这里可以自定义密码的解密规则,此处采用sm4加密为例,可以替换成自己的解密类
String key = "86C63180C2806ED1F47B859DE501215B";
poolProperties.setPassword(
Sm4Util.decryptEcb(key, getOrDefault(password, index, password.get(0)).trim()));
//-------------------------------------------------------------------------------------------
HikariDataSource ds = poolProperties.getDataSource();
ds.setConnectionTestQuery(TEST_QUERY);
dataSources.add(ds);
callback.accept(ds);
}
Preconditions.checkArgument(CollectionUtils.isNotEmpty(dataSources), "no datasource available");
return dataSources;
}
同时这个类中其他所有的属性都可以根据自己的需要去进行修改,也可以自己实现这个实现类的父接口,不过只是修改密码规则的话还是建议直接在这里修改
sm4工具类
依赖:
<!--sm4加密算法-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.66</version>
</dependency>
代码:
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Arrays;
@SuppressWarnings("all") // nacos一键打包时跳过检查,不加可能会报错
public class Sm4Util {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ENCODING = "UTF-8";
public static final String ALGORITHM_NAME = "SM4";
//加密算法/分组加密模式/分组填充方式
//PKCS5Padding-以8个字节为一组分组加密
//定义分组加密模式使用:PKCS5Padding
public static final String ALGORITHM_NAME_ECB_PADDING = "SM4/ECB/PKCS5Padding";
//128-32位16进制;256-64位16进制
public static final int DEFAULT_KEY_SIZE = 128;
/**
* 生成ECB暗号
*
* @param algorithmName 算法名称
* @param mode 模式
* @param key
* @return
* @throws Exception
* @explain ECB模式(电子密码本模式:Electronic codebook)
*/
private static Cipher generateEcbCipher(String algorithmName, int mode, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
cipher.init(mode, sm4Key);
return cipher;
}
public static byte[] generateKey() throws Exception {
return generateKey(DEFAULT_KEY_SIZE);
}
public static byte[] generateKey(int keySize) throws Exception {
KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
kg.init(keySize, new SecureRandom());
return kg.generateKey().getEncoded();
}
/**
* sm4加密
*
* @param hexKey 16进制秘钥(忽略大小写)
* @param paramStr 待加密字符串
* @return 返回16进制的加密字符串
* @throws Exception
*/
public static String encryptEcb(String hexKey, String paramStr) throws Exception {
String cipherText = "";
// 16进制字符串-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// String-->byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 加密后的数组
byte[] cipherArray = encrypt_Ecb_Padding(keyData, srcData);
// byte[]-->hexString
cipherText = ByteUtils.toHexString(cipherArray);
return cipherText;
}
/**
* 加密模式之Ecb
*
* @param key
* @param data
* @return
* @throws Exception
* @explain
*/
public static byte[] encrypt_Ecb_Padding(byte[] key, byte[] data) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(data);
}
/**
* sm4解密
*
* @param hexKey 16进制密钥
* @param cipherText 16进制的加密字符串(忽略大小写)
* @return 解密后的字符串
* @throws Exception
* @explain 解密模式:采用ECB
*/
public static String decryptEcb(String hexKey, String cipherText) throws Exception {
// 用于接收解密后的字符串
String decryptStr = "";
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// hexString-->byte[]
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] srcData = decrypt_Ecb_Padding(keyData, cipherData);
// byte[]-->String
decryptStr = new String(srcData, ENCODING);
return decryptStr;
}
/**
* @param key
* @param cipherText
* @return
* @throws Exception
*/
public static byte[] decrypt_Ecb_Padding(byte[] key, byte[] cipherText) throws Exception {
Cipher cipher = generateEcbCipher(ALGORITHM_NAME_ECB_PADDING, Cipher.DECRYPT_MODE, key);
return cipher.doFinal(cipherText);
}
/**
* 校验加密前后的字符串是否为同一数据
*
* @param hexKey 16进制密钥(忽略大小写)
* @param cipherText 16进制加密后的字符串
* @param paramStr 加密前的字符串
* @return 是否为同一数据
* @throws Exception
* @explain
*/
public static boolean verifyEcb(String hexKey, String cipherText, String paramStr) throws Exception{
// 用于接收校验结果
boolean flag = false;
// hexString-->byte[]
byte[] keyData = ByteUtils.fromHexString(hexKey);
// 将16进制字符串转换成数组
byte[] cipherData = ByteUtils.fromHexString(cipherText);
// 解密
byte[] decryptData = decrypt_Ecb_Padding(keyData, cipherData);
// 将原字符串转换成byte[]
byte[] srcData = paramStr.getBytes(ENCODING);
// 判断2个数组是否一致
flag = Arrays.equals(decryptData, srcData);
return flag;
}
public static void main(String[] args) throws Exception {
String key = "86C63180C2806ED1F47B859DE501215B";
String s = "QAZXSWEDCVFR";
String jiami = Sm4Util.encryptEcb(key, s);
System.out.println("加密====" + jiami);
String jiemi = Sm4Util.decryptEcb(key, jiami);
System.out.println("解密====" + jiemi);
byte[] bs = ByteUtils.fromHexString(key);
System.out.println(Arrays.toString(bs));
System.out.println(ByteUtils.toHexString(generateKey()));
String str = "86C63180C2806ED1F47B859DE501215B";
int byte_len = str.getBytes("utf-8").length;
int len = str.length();
System.out.println("字节长度为:" + byte_len);
System.out.println("字符长度为:" + len);
System.out.println("系统默认编码方式:" + System.getProperty("file.encoding"));
}
}
打包发布
- 修改完源码需要将源码包打包成可以使用的jar包
在nacos顶级包路径下打开控制台,输入以下命令
mvn -Prelease-nacos -Dmaven.test.skip=true -Drat.skip=true clean install -U
- (也可以在IDEA的terminal输入命令进行打包操作)
- 然后等待打包成功
打包好的包位置:
nacos-xxx\distribution\target\nacos-server-xxx.zip(windows)
nacos-xxx\distribution\target\nacos-server-xxx.tar.gz(linux) - 修改配置文件(nacos-server-1.4.1\nacos\conf\application.properties)中的密码,用自己定义的加密方式加密一遍
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=b916909c4b06919ed4435b0d4873ecd1
- 启动nacos,登录到控制台,日志没有报错那么修改nacos密码连接方式就完成了
启动:在nacos-server-1.4.1\nacos\bin打开cmd命令窗口,输入命令
startup.cmd -m standalone
(standalone代表着单机模式运行,非集群模式)
看到"Nacos started successfully in stand alone mode.”后表示服务已启动
- 登录到控制台,nacos默认使用8848端口,可通过 http://127.0.0.1:8848/nacos/index.html 进入自带的控制台界面,默认用户名/密码是nacos/nacos
Nacos源码Idea启动流程
下载Nacos源码
这里推荐下载1.4.1版本,2.0.0版本刚刚出来稳定性还不够。
编译运行
下载完成之后,把项目导入到Idea中,找到Nacos的主启动类,如下图所示:
点击启动,会出现缺少com.alibaba.nacos.consistency.entity的错误。
出现的原因是因为nacos中用的是proto文件,而代码中没有生成对应的实体类,因此我们需要手动生成。方式如下。
安装protoc
protoc安装包下载地址(https://github.com/protocolbuffers/protobuf/releases),目前最新版本是3.17.0,下载方式点击对应的操作系统的zip包即可。windows版本下载如下:
下载完成之后,解压到自己喜欢的目录。然后将目录地址下的bin文件夹配置到系统环境变量(path)中,配置完成之后在cmd中输入【protoc --version】检查是否配置成功。
反编译成对应的java实体类
进入到源码的consistency/src/main/proto/路径下,使用cmd命令,依次运行如下指令
protoc --java_out=../java/ ./consistency.proto
protoc --java_out=../java/ ./Data.proto
执行完成之后,可以在com.alibaba.nacos.consistency包下面看到对应的entity包,如下所示:
启动运行
配置数据库信息
配置方式和nacos软件启动的时候是一样的,首先需要一个本地MySQL数据库,建立数据库nacos_config,如果之前配置过可以不用配置。
- 在本地数据库新建数据库:nacos
- 初始化数据库:在nacos数据库中执行 distribution/conf/nacos-mysql.sql 数据库脚本
修改项目中application.properties文件中的数据库连接部分,需要修改distribution.conf文件和console项目中resources下的application.properties,如下图示:
#*************** Config Module Related Configurations ***************#
### If use MySQL as datasource:
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=b916909c4b06919ed4435b0d4873ecd1
配置单机启动参数
在Nacos应用程序的配置参数中添加:
-Dnacos.standalone=true -Dnacos.home=E:\code\project\source\nacos\nacos-1.4.1\distribution
第一个参数含义为单机模式启动,第二个参数为nacos.home地址(源码中distribution文件夹的位置)不可以配置错误。
配置完成之后启动即可,启动成功日志如下: