Commit 293e7d19 by 郑冰晶

数据库加密组件

parent 7d00ea38
...@@ -17,9 +17,31 @@ public class BizResponse<T> extends Response<T> { ...@@ -17,9 +17,31 @@ public class BizResponse<T> extends Response<T> {
super(code, msg); super(code, msg);
} }
public BizResponse(Integer code, String msg, T data) {
super(code, msg, data);
}
public boolean isSuccess() { public boolean isSuccess() {
return getCode() != null && getCode() == CommonConstant.Success.CODE; return getCode() != null && getCode().equals(CommonConstant.Success.CODE);
}
public static <T> BizResponse<T> success(T data) {
return new BizResponse<>(CommonConstant.Success.CODE, CommonConstant.Success.MSG, data);
}
public static <T> BizResponse<T> success(Integer code, String msg) {
return new BizResponse<>(code, msg);
} }
} public static <T> BizResponse<T> success(Integer code, String msg,T data) {
return new BizResponse<>(code, msg, data);
}
public static <T> BizResponse<T> fail(Integer code, String msg) {
return new BizResponse<>(code, msg);
}
public static <T> BizResponse<T> fail(Integer code, String msg, T data) {
return new BizResponse<>(code, msg, data);
}
}
\ No newline at end of file
...@@ -21,6 +21,12 @@ public class Response<T> implements Serializable { ...@@ -21,6 +21,12 @@ public class Response<T> implements Serializable {
this.msg = msg; this.msg = msg;
} }
public Response(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() { public Integer getCode() {
return code; return code;
} }
...@@ -53,4 +59,4 @@ public class Response<T> implements Serializable { ...@@ -53,4 +59,4 @@ public class Response<T> implements Serializable {
", data=" + data + ", data=" + data +
'}'; '}';
} }
} }
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>matrix-datasource-security</artifactId>
<groupId>com.secoo.mall</groupId>
<version>2.0.17.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>matrix-datasource-security-core</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
</dependencies>
<build>
<finalName>matrix-datasource-security-starter</finalName>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.secoo.mall.datasource.security.algorithm;
import com.secoo.mall.datasource.security.constant.SecurityType;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import org.apache.commons.codec.digest.DigestUtils;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* AES encrypt algorithm.
*/
@Getter
@Setter
public final class AESSecurityAlgorithm implements SecurityAlgorithm {
private String aesKey;
private byte[] secretKey;
public AESSecurityAlgorithm(String aesKey){
if(aesKey == null || aesKey.length() == 0){
throw new SecurityException("aesKey can not be null!");
}
this.aesKey = aesKey;
this.secretKey = createSecretKey();
}
private byte[] createSecretKey() {
return Arrays.copyOf(DigestUtils.sha1(aesKey), 16);
}
@SneakyThrows(GeneralSecurityException.class)
@Override
public String encrypt(final Object plaintext) {
if (null == plaintext) {
return null;
}
byte[] result = getCipher(Cipher.ENCRYPT_MODE).doFinal(String.valueOf(plaintext).getBytes(StandardCharsets.UTF_8));
return DatatypeConverter.printBase64Binary(result);
}
@SneakyThrows(GeneralSecurityException.class)
@Override
public String decrypt(final String ciphertext) {
if (null == ciphertext) {
return null;
}
byte[] result = getCipher(Cipher.DECRYPT_MODE).doFinal(DatatypeConverter.parseBase64Binary(ciphertext));
return new String(result, StandardCharsets.UTF_8);
}
private Cipher getCipher(final int decryptMode) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
Cipher result = Cipher.getInstance(getType());
result.init(decryptMode, new SecretKeySpec(secretKey, getType()));
return result;
}
public String getType() {
return SecurityType.AES;
}
}
package com.secoo.mall.datasource.security.algorithm;
/**
* Encrypt|decrypt algorithm for SPI.
*/
public interface SecurityAlgorithm {
/**
* Encode.
*
* @param plainText plainText
* @return cipherText
*/
String encrypt(Object plainText);
/**
* Decode.
*
* @param cipherText cipherText
* @return plainText
*/
String decrypt(String cipherText);
}
package com.secoo.mall.datasource.security.constant;
public class DBType {
public static final String MYSQL = "MYSQL";
public static final String ORACLE = "ORACLE";
}
package com.secoo.mall.datasource.security.constant;
public class SecurityType {
public static final String DES = "DES";
public static final String AES = "AES";
}
package com.secoo.mall.datasource.security.constant;
public class SymbolConstants {
public static final String SEPARATOR = ",";
public static final String EQUAL = "=";
public static final String SEPARATOR_FEN = ";";
}
package com.secoo.mall.datasource.security.exception;
public class SecurityException extends RuntimeException{
public SecurityException(String message) {
super(message);
}
public SecurityException(Throwable e) {
super(e);
}
public SecurityException(String message, Throwable e) {
super(message, e);
}
}
package com.secoo.mall.datasource.security.exception;
public class TableColumnException extends RuntimeException {
private String message;
public TableColumnException(String message) {
this.message = message;
}
public TableColumnException(String message, String message1) {
super(message);
this.message = message1;
}
public TableColumnException(String message, Throwable cause, String message1) {
super(message, cause);
this.message = message1;
}
public TableColumnException(Throwable cause, String message) {
super(cause);
this.message = message;
}
public TableColumnException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, String message1) {
super(message, cause, enableSuppression, writableStackTrace);
this.message = message1;
}
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
\ No newline at end of file
package com.secoo.mall.datasource.security.filter;
import com.alibaba.druid.filter.FilterEventAdapter;
import com.alibaba.druid.proxy.jdbc.ResultSetProxy;
import com.alibaba.druid.proxy.jdbc.StatementProxy;
import com.secoo.mall.datasource.security.rule.ColumnRule;
import com.secoo.mall.datasource.security.rule.TableRule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbsSecurityFilter extends FilterEventAdapter {
private static final Logger log = LoggerFactory.getLogger(AbsSecurityFilter.class);
public static Charset charset = StandardCharsets.UTF_8;
private String dbType;
/**
* 加密是否开启
*/
private boolean enabled = true;
/**
* 是否启用并行处理
*/
private boolean parallelEnabled;
/**
* 建议core=max
*/
private int corePoolSize;
/**
* 建议core=max
*/
private int maxPoolSize;
private Map<String, Map<String, ColumnRule>> tableRuleMap;
private ExecutorService parallelExecutor;
public String getDbType() {
return dbType;
}
public void setDbType(String dbType) {
this.dbType = dbType;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isParallelEnabled() {
return parallelEnabled;
}
public void setParallelEnabled(boolean parallelEnabled) {
this.parallelEnabled = parallelEnabled;
}
public int getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public Map<String, Map<String, ColumnRule>> getTableRuleMap() {
return tableRuleMap;
}
public ExecutorService getParallelExecutor() {
return parallelExecutor;
}
protected void resultSetOpenAfter(ResultSetProxy resultSet) {
if (!enabled) {
return;
}
decryptResultSet(resultSet);
}
protected abstract void decryptResultSet(ResultSetProxy resultSet);
protected void statementExecuteBefore(StatementProxy statement, String sql) {
if (!enabled) {
return;
}
this.encryptStatement(statement, sql);
}
protected abstract void encryptStatement(StatementProxy statement,String sql);
private Map<String, Map<String, ColumnRule>> parseTableRules(Set<TableRule> tableRules) {
if (tableRules == null || tableRules.size() == 0) {
return null;
}
Map<String, Map<String, ColumnRule>> tableRuleMap = new HashMap<>();
for(TableRule tableRule: tableRules){
if(tableRule == null || tableRule.getColumnRules() == null || tableRule.getColumnRules().isEmpty()){
continue;
}
Map<String, ColumnRule> columnRuleMap = new HashMap<>();
for(ColumnRule columnRule:tableRule.getColumnRules()){
columnRuleMap.put(columnRule.getLogicColumn(),columnRule);
}
tableRuleMap.put(tableRule.getTableName(),columnRuleMap);
}
return tableRuleMap;
}
public void init(Set<TableRule> tableRules) {
if (enabled) {
return;
}
if (tableRules == null || tableRules.isEmpty()) {
throw new SecurityException("security tableRules is null");
}
this.tableRuleMap = this.parseTableRules(tableRules);
log.info("security tableRules is enable:{}", tableRuleMap);
if (parallelEnabled) {
if (corePoolSize <= 0 || maxPoolSize <= 0) {
throw new SecurityException("corePoolSize(" + corePoolSize + ") or maxPoolSize(" + maxPoolSize + ") is invalid");
}
log.debug("init security parallelExecutors : corePoolSize={}, maxPoolSize={}", this.corePoolSize, this.maxPoolSize);
this.parallelExecutor = new ThreadPoolExecutor(
this.corePoolSize,
this.maxPoolSize,
300,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
new NamedThreadFactory("matrix-security-"),
new ThreadPoolExecutor.CallerRunsPolicy()
);
}
}
public void destroy() {
if(this.parallelExecutor != null){
try {
parallelExecutor.shutdown();
parallelExecutor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
log.warn("interrupted when shutdown the executor:", e);
}
}
}
static class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
NamedThreadFactory(String namePrefix) {
this.namePrefix = namePrefix;
}
public Thread newThread(Runnable runnable) {
return new Thread(runnable, namePrefix + threadNumber.getAndIncrement());
}
}
}
package com.secoo.mall.datasource.security.rule;
import com.secoo.mall.datasource.security.algorithm.AESSecurityAlgorithm;
import com.secoo.mall.datasource.security.algorithm.SecurityAlgorithm;
public class ColumnRule {
private String encryptType;
/**
* 加密器配置
*/
private String encryptKey;
/**
* 逻辑字段名称
*/
private String logicColumn;
/**
* 明文字段名称
*/
private String plainColumn;
/**
* 加密字段名称
*/
private String cipherColumn;
/**
* 加密器
*/
private SecurityAlgorithm securityAlgorithm;
public String getEncryptKey() {
return encryptKey;
}
public void setEncryptKey(String encryptKey) {
this.encryptKey = encryptKey;
this.securityAlgorithm = new AESSecurityAlgorithm(encryptKey);
}
public String getLogicColumn() {
return logicColumn;
}
public void setLogicColumn(String logicColumn) {
this.logicColumn = logicColumn;
}
public String getPlainColumn() {
return plainColumn;
}
public void setPlainColumn(String plainColumn) {
this.plainColumn = plainColumn;
}
public String getCipherColumn() {
return cipherColumn;
}
public void setCipherColumn(String cipherColumn) {
this.cipherColumn = cipherColumn;
}
public SecurityAlgorithm getSecurityAlgorithm() {
return securityAlgorithm;
}
public void setSecurityAlgorithm(SecurityAlgorithm securityAlgorithm) {
this.securityAlgorithm = securityAlgorithm;
}
}
package com.secoo.mall.datasource.security.rule;
import java.util.Set;
public class DataSourceRule {
/**
* 数据库名
*/
private String datasourceName;
/**
* 表规则
*/
private Set<TableRule> tableRules;
public String getDatasourceName() {
return datasourceName;
}
public void setDatasourceName(String datasourceName) {
this.datasourceName = datasourceName;
}
public Set<TableRule> getTableRules() {
return tableRules;
}
public void setTableRules(Set<TableRule> tableRules) {
this.tableRules = tableRules;
}
}
package com.secoo.mall.datasource.security.rule;
import java.util.Set;
public class TableRule {
/**
* 表名
*/
private String tableName;
/**
* 列规则
*/
private Set<ColumnRule> columnRules;
public String getTableName() {
return tableName;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public Set<ColumnRule> getColumnRules() {
return columnRules;
}
public void setColumnRules(Set<ColumnRule> columnRules) {
this.columnRules = columnRules;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>matrix-datasource-security</artifactId>
<groupId>com.secoo.mall</groupId>
<version>2.0.17.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>matrix-datasource-security-starter</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.secoo.mall</groupId>
<artifactId>matrix-datasource-security-core</artifactId>
</dependency>
<dependency>
<groupId>com.secoo.mall</groupId>
<artifactId>matrix-datasource-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<finalName>matrix-datasource-security-starter</finalName>
<plugins>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
package com.secoo.mall.datasource.security.config;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.pool.DruidDataSource;
import com.secoo.mall.datasource.bean.MatrixDataSource;
import com.secoo.mall.datasource.security.constant.DBType;
import com.secoo.mall.datasource.security.filter.MysqlSecurityFilter;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.Map;
@Configuration
@EnableConfigurationProperties(DataSourceSecurityProperties.class)
public class DataSourceSecurityAutoConfiguration implements ApplicationContextAware, SmartInitializingSingleton {
private ConfigurableApplicationContext applicationContext;
private DataSourceSecurityProperties properties;
DataSourceSecurityAutoConfiguration(DataSourceSecurityProperties properties){
this.properties = properties;
}
@Override
public void afterSingletonsInstantiated() {
if(properties == null || properties.getDatasourceRules() == null || properties.getDatasourceRules().size() == 0){
throw new RuntimeException("DataSourceSecurityProperties is null!");
}
Map<String, DataSource> dataSourceBeans = this.applicationContext.getBeansOfType(DataSource.class);
properties.getDatasourceRules().forEach(datasourceRule -> {
DataSource dataSource = dataSourceBeans.get(datasourceRule.getDatasourceName());
if(dataSource == null){
return;
}
DruidDataSource druidDataSource = null;
if(dataSource instanceof DruidDataSource){
druidDataSource = (DruidDataSource) dataSource;
}else if(dataSource instanceof MatrixDataSource){
druidDataSource = (DruidDataSource) ((MatrixDataSource) dataSource).getTargetDataSource(((MatrixDataSource) dataSource).getDsName());
}
if(druidDataSource == null){
return;
}
if(druidDataSource.getDbType().equals(DBType.MYSQL)){
MysqlSecurityFilter securityFilter = new MysqlSecurityFilter();
securityFilter.setEnabled(properties.getEnabled() != null && properties.getEnabled());
securityFilter.setParallelEnabled(properties.getParallelEnabled() != null && properties.getParallelEnabled());
if(securityFilter.isParallelEnabled()){
securityFilter.setCorePoolSize(properties.getCorePoolSize() == null?0:properties.getCorePoolSize());
securityFilter.setMaxPoolSize(properties.getMaxPoolSize() == null?Integer.MAX_VALUE:properties.getMaxPoolSize());
}
securityFilter.init(datasourceRule.getTableRules());
druidDataSource.setProxyFilters(new ArrayList<Filter>(){{add(securityFilter);}});
}
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = (ConfigurableApplicationContext) applicationContext;
}
}
package com.secoo.mall.datasource.security.config;
import com.secoo.mall.datasource.security.rule.DataSourceRule;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Set;
@ConfigurationProperties(prefix = DataSourceSecurityProperties.PREFIX)
public class DataSourceSecurityProperties {
public static final String PREFIX = "spring.matrix.security";
private Boolean enabled = true;
/**
* 是否启用并行处理,默认不启用
*/
private Boolean parallelEnabled = false;
/**
* 建议core=max
*/
private Integer corePoolSize;
/**
* 建议core=max
*/
private Integer maxPoolSize;
/**
* 加解密规则
*/
public Set<DataSourceRule> datasourceRules;
public Boolean getEnabled() {
return enabled;
}
public void setEnabled(Boolean enabled) {
this.enabled = enabled;
}
public Boolean getParallelEnabled() {
return parallelEnabled;
}
public void setParallelEnabled(Boolean parallelEnabled) {
this.parallelEnabled = parallelEnabled;
}
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
public Integer getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(Integer maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public Set<DataSourceRule> getDatasourceRules() {
return datasourceRules;
}
public void setDatasourceRules(Set<DataSourceRule> datasourceRules) {
this.datasourceRules = datasourceRules;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>matrix-datasource</artifactId>
<groupId>com.secoo.mall</groupId>
<version>2.0.17.RELEASE</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>matrix-datasource-security</artifactId>
<packaging>pom</packaging>
<modules>
<module>matrix-datasource-security-core</module>
<module>matrix-datasource-security-starter</module>
</modules>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.secoo.mall</groupId>
<artifactId>matrix-datasource-security-core</artifactId>
<version>2.0.17.RELEASE</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<modules> <modules>
<module>matrix-datasource-core</module> <module>matrix-datasource-core</module>
<module>matrix-datasource-druid</module> <module>matrix-datasource-druid</module>
<module>matrix-datasource-security</module>
</modules> </modules>
<dependencyManagement> <dependencyManagement>
......
...@@ -210,11 +210,6 @@ ...@@ -210,11 +210,6 @@
<artifactId>joda-time</artifactId> <artifactId>joda-time</artifactId>
<version>2.10</version> <version>2.10</version>
</dependency> </dependency>
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>4.0.2</version>
</dependency>
<!--protobuf--> <!--protobuf-->
<dependency> <dependency>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment