springboot+druid+passwordEncrypt

安全

最近在读《大型网站技术架构:核心原理与案例分析》,并且在极客时间看了一场知道创宇研发中心潘少华的分享,分享最新区块链安全态势,发现安全问题不可忽视,比如我们在 showdoc 上分享线上接口 API,或者将代码托管到码云的企业级管理账号上,总是存在着各种风险,我们能做的就是尽量暴露少量的核心信息,数据库连接密码就是最为关键的之一

加密策略
  • 根据下面代码可以知道,公约,私钥,加密后的密码的生产策略
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ConfigTools {

private static final String DEFAULT_PRIVATE_KEY_STRING = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAocbCrurZGbC5GArEHKlAfDSZi7gFBnd4yxOt0rwTqKBFzGyhtQLu5PRKjEiOXVa95aeIIBJ6OhC2f8FjqFUpawIDAQABAkAPejKaBYHrwUqUEEOe8lpnB6lBAsQIUFnQI/vXU4MV+MhIzW0BLVZCiarIQqUXeOhThVWXKFt8GxCykrrUsQ6BAiEA4vMVxEHBovz1di3aozzFvSMdsjTcYRRo82hS5Ru2/OECIQC2fAPoXixVTVY7bNMeuxCP4954ZkXp7fEPDINCjcQDywIgcc8XLkkPcs3Jxk7uYofaXaPbg39wuJpEmzPIxi3k0OECIGubmdpOnin3HuCP/bbjbJLNNoUdGiEmFL5hDI4UdwAdAiEAtcAwbm08bKN7pwwvyqaCBC//VnEWaq39DCzxr+Z2EIk=";
public static final String DEFAULT_PUBLIC_KEY_STRING = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKHGwq7q2RmwuRgKxBypQHw0mYu4BQZ3eMsTrdK8E6igRcxsobUC7uT0SoxIjl1WveWniCASejoQtn/BY6hVKWsCAwEAAQ==";

public static void main(String[] args) throws Exception {
String password = args[0];
String[] arr = genKeyPair(512);
System.out.println("privateKey:" + arr[0]);
System.out.println("publicKey:" + arr[1]);
System.out.println("password:" + encrypt(arr[0], password));
}
...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
➜  1.1.10 ll
total 5456
-rw-r--r-- 1 niuhesm staff 195B 7 28 15:05 _remote.repositories
-rw-r--r-- 1 niuhesm staff 2.6M 7 28 15:04 druid-1.1.10.jar
-rw-r--r-- 1 niuhesm staff 40B 7 28 15:04 druid-1.1.10.jar.sha1
-rw-r--r-- 1 niuhesm staff 18K 7 28 15:04 druid-1.1.10.pom
-rw-r--r-- 1 niuhesm staff 40B 7 28 15:04 druid-1.1.10.pom.sha1
➜ 1.1.10 pwd
/Users/niuhesm/.m2/repository/com/alibaba/druid/1.1.10
➜ 1.1.10 java -cp druid-1.1.10.jar com.alibaba.druid.filter.config.ConfigTools 123456
privateKey:MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAuTqQKLAvphok1rS1/NTvdWb8dejoPF144uP8hE4GjDvTeCFTkAGE1di4b+wlRtBJT/TWElQMPUXnypDQbZTyFwIDAQABAkAR0HcS4rcCc5s9Zw3lrhkFooz/ThIf1CGPOLwNgW+RxvlMGEvPRjMDmZFbSLPvmdO3J3RCy2eygzoJQu6djrUhAiEA4PHsbva76Qt2iHaruX/kDvzBWwMBvDeG6kkXvIDc9tECIQDSzPm4Tfhi+j7k7AyWKtjCl055JAyOP7UjlVXUGQRkZwIgIaMLf+xVXRvhtbZJJ4wARl11bG6eq86B1jbn3cBHSoECIEdj+dKMPWmv3GsE8kJNInnMalwmdEYcl0kEwzuAeXdTAiBFIqi7wiCi0zeNkMLv3L4sI+Sy0bsaZ/U/6jRbw7WIYQ==
publicKey:MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALk6kCiwL6YaJNa0tfzU73Vm/HXo6DxdeOLj/IROBow703ghU5ABhNXYuG/sJUbQSU/01hJUDD1F58qQ0G2U8hcCAwEAAQ==
password:SPhksKFVhjsnWJ21HJASdn8ekZ/izn22D5ed+waMKxjjsWVn1KRI/px4vY2kRDsIAvm8uaMQG9lu7kWAWpP/jQ==
解密配置
  • 关键
    • 此时就需要通过 Java config 相关的 DataSource,并通过重写 DruidPasswordCallback&setProperties 方法来重新设置解密后的密码
    • ConfigTools.decrypt(publickey, password); 提供对应的解密算法
    • spring.datasource.druid.password-callback=com.lckjep.yxd.crm.config.DbPasswordCallback
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @Description: 数据库密码回调
* @author: <a href="smniuhe@gmail.com"/>smniuhe</a>
*/
public class DbPasswordCallback extends DruidPasswordCallback {


private static final Logger LOGGER = LoggerFactory.getLogger(DbPasswordCallback.class);

@Override
public void setProperties(Properties properties) {
super.setProperties(properties);
String password = (String) properties.get("password");
String publickey = (String) properties.get("publickey");
try {
String dbpassword = ConfigTools.decrypt(publickey, password);
System.out.println("===============" + dbpassword);
setPassword(dbpassword.toCharArray());
} catch (Exception e) {
LOGGER.error("Druid ConfigTools.decrypt", e);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
/**
* servlet,filter 组件注册(servlet 3.0 后时代)
* @Description: spring boot项目启动的时候,动态注册相关 servlet,filter
* @author <a href="smniuhe@gmail.com">smniuhe</a>
*/
@Configuration
public class DruidConfig {

private Logger logger = LoggerFactory.getLogger(getClass());

@Value("${spring.datasource.druid.url}")
private String dbUrl;

@Value("${spring.datasource.druid.username}")
private String username;

@Value("${spring.datasource.druid.password}")
private String password;

@Value("${spring.datasource.druid.driver-class-name}")
private String driverClassName;

@Value("${spring.datasource.type}")
private String dbType;

@Value("${spring.datasource.druid.initial-size}")
private int initialSize;

@Value("${spring.datasource.druid.min-idle}")
private int minIdle;

@Value("${spring.datasource.druid.max-active}")
private int maxActive;

@Value("${spring.datasource.druid.max-wait}")
private int maxWait;

@Value("${spring.datasource.druid.time-between-eviction-runs-millis}")
private int timeBetweenEvictionRunsMillis;

@Value("${spring.datasource.druid.min-evictable-idle-time-millis}")
private int minEvictableIdleTimeMillis;

@Value("${spring.datasource.druid.validation-query}")
private String validationQuery;

@Value("${spring.datasource.druid.test-while-idle}")
private boolean testWhileIdle;

@Value("${spring.datasource.druid.test-on-borrow}")
private boolean testOnBorrow;

@Value("${spring.datasource.druid.test-on-return}")
private boolean testOnReturn;

@Value("${spring.datasource.druid.pool-prepared-statements}")
private boolean poolPreparedStatements;

@Value("${spring.datasource.druid.max-pool-prepared-statement-per-connection-size}")
private int maxPoolPreparedStatementPerConnectionSize;

@Value("${spring.datasource.druid.filters}")
private String filters;

@Value("${spring.datasource.druid.connection-properties}")
private String connectionProperties;

@Value("${spring.datasource.druid.password-callback}")
private String passwordCallbackClassName;


@Bean
public DataSource dataSource() {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
datasource.setConnectionProperties(connectionProperties);
datasource.setDbType(dbType);
try {
datasource.setPasswordCallbackClassName(passwordCallbackClassName);
} catch (Exception e) {
logger.error("druid configuration initialization passwordCallbackClassName", e);
}
try {
datasource.setFilters(filters);
} catch (SQLException e) {
logger.error("druid configuration initialization filter", e);
}
return datasource;
}


@Bean
public ServletRegistrationBean druidServlet() {

ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//登录查看信息的账号密码.

servletRegistrationBean.addInitParameter("loginUsername","admin");

servletRegistrationBean.addInitParameter("loginPassword","123456");
return servletRegistrationBean;
}

@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new WebStatFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 数据源基础配置
spring.datasource.druid.url=jdbc:mysql://localhost:3306/yxp2p?useSSL=false&characterEncoding=utf8
spring.datasource.druid.username=root
spring.datasource.druid.password=SPhksKFVhjsnWJ21HJASdn8ekZ/izn22D5ed+waMKxjjsWVn1KRI/px4vY2kRDsIAvm8uaMQG9lu7kWAWpP/jQ==

spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
# 初始化大小,最小,最大
spring.datasource.druid.initial-size=1
spring.datasource.druid.max-active=20
spring.datasource.druid.min-idle=1
# 配置获取连接等待超时的时间
spring.datasource.druid.max-wait=60000
# 打开PSCache,并且指定每个连接上PSCache的大小
spring.datasource.druid.pool-prepared-statements=false
spring.datasource.druid.max-pool-prepared-statement-per-connection-size=20
spring.datasource.druid.validation-query=SELECT * FROM rd_system WHERE id = 1
spring.datasource.druid.test-on-borrow=false
spring.datasource.druid.test-on-return=false
spring.datasource.druid.test-while-idle=true
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
spring.datasource.druid.time-between-eviction-runs-millis=60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
spring.datasource.druid.min-evictable-idle-time-millis=300000
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计
spring.datasource.druid.filters=stat
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.password-callback=com.lckjep.yxd.crm.config.DbPasswordCallback
spring.datasource.publicKey=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBALk6kCiwL6YaJNa0tfzU73Vm/HXo6DxdeOLj/IROBow703ghU5ABhNXYuG/sJUbQSU/01hJUDD1F58qQ0G2U8hcCAwEAAQ==
8zj7+9vU3jjqItds740GMbZJkuqCECAwEAAQ==
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.druid.connection-properties=config.decrypt=true;publickey=${spring.datasource.publicKey};password=${spring.datasource.druid.password}