前言
数据库密码的安全问题一直是大家关注的问题,也是黑客主要的攻击手段,在历史上也发生了很多因为数据库密码泄露而导致用户信息被泄露,从而导致公司的经济损失,比如华住酒店泄露数据库密码案例。很多公司的数据库密码都是以明文的形式配置在代码里或者配置中心里面,如果你的代码不小心泄露了,那么你的数据库密码也就泄露了,并且你的数据库没有做安全隔离,那么别人就可以登录你的数据库进行操作,而且权限是比较高的,还有是内部人员知道了密码,这个也是不安全的。
解决方案
主要的解决方案就是对数据库密码进行加密,在配置文件里面不存储明文,在使用的时候进行解密。
方案一:使用数据库连接池框架自带的密码解密方案
比较依赖框架,如果框架没有开放功能,但是你需要这个功能,那么就需要修改源码了,像Druid是框架支持的。
方案二:使用spring解析properties对密码进行解密
该方案比较通用,不受框架影响,是在连接池框架的上一步就把事情给处理了。
这里推荐方案二
密码层面的加密:1、硬编码写死进行解密;(不推荐,代码泄露还是存在风险)2、提供密钥管理服务中心,解密都通过RPC/HTTP调用,通过appid等信息进行获取。(推荐使用)
具体实现
Druid实现
- 以下是伪代码,基于代码的
public class MyPasswordCallback extends DruidPasswordCallback {
@Override
public char[] getPassword() {
//公钥
String publicKey="...";
//密码
String password="...";
//解密
return DesUtils.decrypt(publicKey,password);
//TODO 这里可以通过RPC/HTTP(必须只支持内网访问)方式调用密钥服务中心的接口来获取解密之后的密码
}
}
还需要在相应的Druid相关的配置里面加上
<property name="passwordCallback" ref="myPasswordCallback"/>
- 还可以使用其他的方式,基于xml的配置,可以参考官网提供的方法,这里就不在重复写了。
https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
HikariCP实现
因为该框架没有提供扩展接口,所以需要继承DataSource,重写获取密码的方法,以下是伪代码
public class MyDataSource extends HikariDataSource {
/**
* 密匙
*/
private final static String PKEY ="...";
@Override
public String getPassword(){
String encPassword = super.getPassword();
try{
// 密文解密,解密方法可以修改
String key = HexUtil.encodeHexStr(PKEY);
SymmetricCrypto aes = new SymmetricCrypto(SymmetricAlgorithm.AES,key.getBytes());
passwordDis = aes.decryptStr(encPassword, "UTF-8");
return passwordDis;
//或者 通过使用RPC/HTTP调用密钥服务中心相关接口
}catch (Exception e){
throw new AppException("数据库密码解密失败!", e);
}
}
}
需要在xml里面替换掉HikariDataSource的配置。
Spring实现
以下为伪代码
@Slf4j
public class MyInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter implements PriorityOrdered, EmbeddedValueResolverAware {
private StringValueResolver resolver;
// 4.x spring使用这个方法
public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return postProcessProperties(pvs, bean, beanName);
}
// 5.x spring使用这个方法
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
int index = 0;
for (PropertyValue propertyValue : pvs.getPropertyValues()) {
Object value = propertyValue.getValue();
//判断是不是需要进行解密
if (value instanceof String && value.startsWith("decrypt://")) {
String newPassword = "des进行解密 or 调用RPC/HTTP调用密钥服务中心接口";
((MutablePropertyValues) pvs).setPropertyValueAt(new PropertyValue(propertyValue, newPassword), index);
}
index++;
}
return pvs;
}
@Override
public int getOrder() {
return 0;
}
@Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
}
}
EmbeddedValueResolverAware是可以用来获取属性文件里面的值,PriorityOrdered是来设置执行的顺序,InstantiationAwareBeanPostProcessorAdapter的postProcessPropertyValues或者postProcessProperties是在设置属性的时候进行处理。
在把他配置到xml里面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="xxxx.MyInstantiationAwareBeanPostProcessor" />
</beans>
这边就是要在配置文件里面约定好,配置的值必须以什么开头,不然程序不知道要对哪些key进行处理,这里很好的用到了约定优于配置的软件设计范式。
总结
通过上面的几种方式对数据库密码进行加解密,确保了你的密码的安全,老板再也不用担心数据库密码泄露了。这边也比较推荐使用基于spring的方式来进行加解密,这样即使换了数据库连接池框架,该功能也不受影响。
注意:本文归作者所有,未经作者允许,不得转载