为了保护用户隐私,我们需要对数据库用户关键数据,入库加密,取出来解密。为了我们系统自身的安全数据库连接用户名和密码都要加解密
####################################
### DB
####################################
#durid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.druid.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.druid.initialSize=5
spring.datasource.druid.minIdle=5
spring.datasource.druid.maxActive=20
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
#####################################
#### DB
####################################
spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/hlj_demo?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true
spring.datasource.druid.username=GCBeAUOZNANpmXfIUPO42qx/dQP80Lae3BI7ABxQN2AzWhgQAG+S6Dhe
spring.datasource.druid.password=GCAfE1p20be+BX5TZsVlFe1/T1bQ+f2IhnjqOQKe7CJT7xgQ8YOQrf7U
####################################
#是否需要数据连接加密
spring.datasource.encrypt=true
package com.fintech.confin.web.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.fintech.confin.sensitivity.KeycenterUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
/**
* @author HealerJean
* @ClassName DateSourceConfig
* @date 2020/4/9 10:43.
* @Description
*/
@Configuration
public class DateSourceConfig {
@Value("${spring.datasource.druid.driver-class-name}")
private String driverClassName;
@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.initialSize}")
private int initialSize;
@Value("${spring.datasource.druid.minIdle}")
private int minIdle;
@Value("${spring.datasource.druid.maxActive}")
private int maxActive;
@Value("${spring.datasource.druid.maxWait}")
private int maxWait;
@Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.druid.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.druid.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.druid.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.druid.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.encrypt}")
private boolean encrypt;
@Bean(name = "dataSource")
public DataSource dataSource(KeycenterUtils keycenterUtils) {
DruidDataSource datasource = new DruidDataSource();
datasource.setDriverClassName(driverClassName);
datasource.setUrl(dbUrl);
if (encrypt) {
datasource.setUsername(keycenterUtils.decrypt(username));
datasource.setPassword(keycenterUtils.decrypt(password));
} else {
datasource.setUsername(username);
datasource.setPassword(password);
}
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);
return datasource;
}
}
KeyCenterUtils
:加解密工具类package com.healerjean.proj.config.keycenter.one;
import org.springframework.stereotype.Service;
import java.util.Base64;
/**
* @author HealerJean
* @ClassName AES
* @date 2020/4/9 14:28.
* @Description
*/
@Service
public class KeyCenterUtils {
/**
* 自己写加密逻辑
*/
public String encrypt(String src) {
try {
String result = Base64.getEncoder().encodeToString(src.getBytes("UTF-8"));
return result;
} catch (Exception e) {
throw new RuntimeException("encrypt fail!", e);
}
}
/**
* 自己写解密逻辑
*/
public String decrypt(String src) {
try {
byte[] asBytes = Base64.getDecoder().decode(src);
String result = new String(asBytes, "UTF-8");
return result;
} catch (Exception e) {
throw new RuntimeException("decrypt fail!", e);
}
}
}
CustomTypeHandler
数据库字段加解密控制器package com.healerjean.proj.config.keycenter.one;
/**
* @author HealerJean
* @ClassName AESTypeHandler
* @date 2020/4/9 14:27.
* @Description
*/
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class CustomTypeHandler<T> extends BaseTypeHandler<T> {
@Autowired
private KeyCenterUtils keyCenterUtils;
public CustomTypeHandler() {
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, this.keyCenterUtils.encrypt((String)parameter));
}
@Override
public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
String columnValue = rs.getString(columnName);
//有一些可能是空字符
return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
}
@Override
public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String columnValue = rs.getString(columnIndex);
return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
}
@Override
public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String columnValue = cs.getString(columnIndex);
return StringUtils.isBlank(columnValue) ? (T)columnValue : (T)this.keyCenterUtils.decrypt(columnValue);
}
}
package com.healerjean.proj.data.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.healerjean.proj.config.keycenter.one.CustomTypeHandler;
import lombok.Data;
import java.util.Date;
@Data
@TableName(autoResultMap = true) //有了这个BaseMapper查询的结果才能解密
public class User {
private Long id;
private String name;
private Integer age;
//有了这个数据库BaseMapper插入的时候才能加密
@TableField(typeHandler = CustomTypeHandler.class)
private String telPhone;
@TableField(typeHandler = CustomTypeHandler.class)
private String email;
private Date createDate;
private Date createTime;
}
如果不是mybatisPlus的 BaseMapper内部的方法,则需要我们自己放入我们自定义的
typeHandler
@Results({
@Result(column = "email", property = "email", typeHandler = CustomTypeHandler.class),
@Result(column = "tel_phone", property = "telPhone", typeHandler = CustomTypeHandler.class)})
@Select("select * from user where id = #{id}")
List<User> selectDncryptList(Long id);
User中的数据都是正常的 。不是密文。因为我们只讲入库的数据设置了密文。并不会改变User对象本身
@Test
public void encrypt(){
List<User> users = null ;
//插入数据
User user = new User();
user.setName("name");
user.setAge(12);
user.setEmail("[email protected]");
user.setTelPhone("18841256");
userMapper.insert(user);
//更新
user.setEmail("[email protected]");
userMapper.updateById(user);
//查询 :列表查询
users = userMapper.selectList(null);
System.out.println(users);
//查询 :根据Id查询
User user1 = userMapper.selectById(user.getId());
System.out.println(user1);
//自定义sql查询
users = userMapper.selectDncryptList(user.getId());
System.out.println(users);
}
因为数据库中是密文,所以查询的时候,需要我们先加密后才能查
// 根据敏感字段查询
Wrapper<User> userWrapper = new QueryWrapper<User>().lambda()
.select(User::getEmail)
.eq(User::getEmail, keyCenterUtils.encrypt("[email protected]"));
users = userMapper.selectList(userWrapper);
System.out.println(users);
ps:介绍一个mybatis操作数据库时的一个类似黑匣子的东西,TypeHandler
闲聊:
在我们平常开发操作数据库时,查询、插入数据等操作行为,有时会报数据类型不匹配异常,就可以得知数据的类型是不唯一的必然是多种不同的数据类型。并且我们必须要明确的一点就是java作为一门编程语言有自己的数据类型,数据库也是有自己的数据类型的。
jdbc数据类型:org.apache.ibatis.type.JdbcType 此枚举就是所有的数据库支持类型
java数据类型:int、long、string、…
一定要分清,例如java重的date数据插入到数据库中,应该是已经转换成了数据库的某种类型,必然跟java已经没有关系了。中间有一些我们看不见的操作做了数据处理。
假设此时的java类型与数据库数据类型是一样的,哪么其他语言中的日期数据插入数据库时又该怎么解释,例如C#操作数据库存入时间类型,C#与java肯定没有关系吧。所以每种语言与数据库之间有种数据类型关系对应。
思考:
因为java与数据库各自有数据类型,所以在将java数据存入数据库前中间是否有其他操作,是我们看不见的,不然java数据怎么知道自己与哪个jdbc数据类型匹配?
答:mybatis框架为每种数据类型做了默认的关系对应,BaseTypeHandler的所有实现类,就是来做这些处理的。
例如:java中的date插入数据库时是jdbc哪种类型,怎么就是这种类型? 中间具体有什么操作?
答:DateTypeHandler就是来解决date数据类型的处理。
源码查看: 看一下源码中具体怎么处理date数据类型的。
先看一下接口:
//此接口作用是用于指定jdbc与java的数据类型间对应关系处理。
public interface TypeHandler<T> {
// 保存操作,数据入库之前时数据处理
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
//下面三个则是,从数据库加载数据后,vo对象封装前的数据处理
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}