我们把MySQL数据的账号信息,Redis的账号信息等都写在属性文件中,有信息暴露的风险,要保证账号密码的安全我们可以通过MD5或者3DES等加密方式来处理,那么怎么来实现呢?
解决思路
其实这个问题的解决思路还是比较清晰,就是在Spring注入DataSource对象或者RedisClient对象之前解密密文信息,并且覆盖掉之前的配置信息。
3DES的工具类
package com.alex;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
/**
* 3DES加密算法,主要用于加密用户id,身份证号等敏感信息,防止破解
*/
public class DESedeUtil {
//秘钥
public static final String KEY = "~@#$y1a2n.&@+n@$%*(1)";
//秘钥长度
private static final int secretKeyLength = 24;
//加密算法
private static final String ALGORITHM = "DESede";
//编码
private static final String CHARSET = "UTF-8";
/**
* 转换成十六进制字符串
* @param key
* @return
*/
public static byte[] getHex(String key){
byte[] secretKeyByte = new byte[24];
try {
byte[] hexByte;
hexByte = new String(DigestUtils.md5Hex(key)).getBytes(CHARSET);
//秘钥长度固定为24位
System.arraycopy(hexByte,0,secretKeyByte,0,secretKeyLength);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return secretKeyByte;
}
/**
* 生成密钥,返回加密串
* @param key 密钥
* @param encodeStr 将加密的字符串
* @return
*/
public static String encode3DES(String key,String encodeStr){
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(getHex(key), ALGORITHM));
return Base64.encodeBase64String(cipher.doFinal(encodeStr.getBytes(CHARSET)));
}catch(Exception e){
e.printStackTrace();
}
return null;
}
/**
* 生成密钥,解密,并返回字符串
* @param key 密钥
* @param decodeStr 需要解密的字符串
* @return
*/
public static String decode3DES(String key, String decodeStr){
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(getHex(key),ALGORITHM));
return new String(cipher.doFinal(new Base64().decode(decodeStr)),CHARSET);
} catch(Exception e){
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String userId = "root";
String encode = DESedeUtil.encode3DES(KEY, userId);
String decode = DESedeUtil.decode3DES(KEY, encode);
System.out.println("用户id>>>"+userId);
System.out.println("用户id加密>>>"+encode);
System.out.println("用户id解密>>>"+decode);
}
}
配置信息
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/flowable_learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
# 对通过3DES对密码加密
spring.datasource.password=vxiPSdZfapc=
在SpringBoot项目启动的时候在在刷新Spring容器之前执行的,所以我们要做的就是在加载完环境配置信息后,获取到配置的spring.datasource.password=vxiPSdZfapc=这个信息,然后解密并修改覆盖就可以了。
然后在属性文件的逻辑其实是通过发布事件触发对应的监听器来实现的
第一种就是写监听器然后监听之后去进行修改数据
第二种就是写后置处理器去修改数据
声明后置处理器(
- 用Stream流可以处理简单,但是他是java8之后的特性
- 不止一个加密的时候可以多个获取解密塞进去,参考:Spring Boot 配置文件加解密原理就这么简单 - 知乎)
package com.alex;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.boot.env.OriginTrackedMapPropertySource;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.MapPropertySource;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySource;
import java.util.HashMap;
import java.util.Map;
/**
* @Auther: alex
* @Date: 2022/3/26 23:19
* @Description:
*/
public class SafetyEncryptProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
environment.getPropertySources().stream()
.filter(m -> m instanceof OriginTrackedMapPropertySource)
.forEach(m -> {
OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource) m;
for (String propertyName : source.getPropertyNames()) {
if ("spring.datasource.password".equals(propertyName)) {
Map<String, Object> map = new HashMap<>();
// 做解密处理
String property = (String) source.getProperty(propertyName);
String s = DESedeUtil.decode3DES(DESedeUtil.KEY, property);
System.out.println("密文:" + property);
System.out.println("解密后的:" + s);
map.put(propertyName, s);
// 注意要添加到前面,覆盖
environment.getPropertySources().addFirst(new MapPropertySource(propertyName, map));
}
}
});
}
}
然后在META-INF/spring.factories文件中注册
org.springframework.boot.env.EnvironmentPostProcessor=com.alex.SafetyEncryptProcessor