具体问题
具体的面试题目是如果我们把MySQL数据的账号信息,Redis的账号信息等都写在属性文件中,有信息暴露的风险,要保证账号密码的安全我们可以通过MD5或者3DES等加密方式来处理,那么怎么来实现呢?
解决方案
其实这个问题的解决思路还是比较清晰,就是在Spring注入DataSource对象或者RedisClient对象之前解密秘钥信息,并且覆盖掉之前的配置信息。
然后我们通过案例代码来演示下,加深大家的理解 首先我们在属性文件中配置加密后的信息
代码语言:javascript复制spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mb?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
# 对通过3DES对密码加密
spring.datasource.password=t5Jd2CzFWEw=
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
mybatis.mapper-locations=classpath:mapper/*.xml
在SpringBoot项目启动的时候在在刷新Spring容器之前执行的,所以我们要做的就是在加载完环境配置信息后,获取到配置的spring.datasource.password=t5Jd2CzFWEw=
这个信息,然后解密并修改覆盖就可以了。
然后在属性文件的逻辑其实是通过发布事件触发对应的监听器来实现的
所以第一个解决方案就是你自定义一个监听器,这个监听器在加载属性文件(ConfigFileApplicationListener)的监听器之后处理,这种方式稍微麻烦点, 还有一种方式就是通过加载属性文件的一个后置处理器来处理,这就以个为例来实现
3DES的工具类
代码语言:javascript复制/**
* 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 = "123456";
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);
}
}
声明后置处理器
代码语言:javascript复制public class SafetyEncryptProcessor implements EnvironmentPostProcessor {
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
for (PropertySource<?> propertySource : environment.getPropertySources()) {
System.out.println("propertySource = " propertySource);
if(propertySource instanceof OriginTrackedMapPropertySource){
OriginTrackedMapPropertySource source = (OriginTrackedMapPropertySource) propertySource;
for (String propertyName : source.getPropertyNames()) {
//System.out.println(propertyName "=" source.getProperty(propertyName));
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文件中注册
代码语言:javascript复制org.springframework.boot.env.EnvironmentPostProcessor=com.bobo.util.SafetyEncryptProcessor
然后启动项目就可以了
到这儿,其实大家就应该清楚了,这个问题考察的其实就SpringBoot的启动原理,如果对于SpringBoot的启动过程很清楚的话,那么对于特定的需求我们要扩展其实都是非常容易的哦