🎉🎉《Spring Boot实战案例合集》目前已更新113个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
存储加密的应用可以显著提高项目的数据安全性,防止数据泄露和篡改。它对于保护用户隐私、遵守法规要求以及维护企业声誉至关重要。通过实施存储加密,可以确保敏感数据在存储过程中得到最大程度的保护。
在项目开发中,我们通常会采用MyBatis、JPA或JDBC来实现数据库操作,而对于不同的持久层实现,我们需要采用相应的方案来实现数据的加解密。
JPA方案:使用@Converter自定义属性转换器,结合加密算法(如AES)对敏感字段加密/解密。实体类字段标注@Convert,持久化时自动加密,查询时解密。
MyBatis方案:通过插件(Interceptor)拦截SQL操作,对参数中的敏感数据动态加密,查询结果解密。我们也可以采用自定义的TypeHandler实现数据的处理。
JDBC方案:针对JDBC,我们则需要在PreparedStatement设置参数时进行数据的加密;查询时通过ResultSet对应的getXxx方法实现数据的解密。
本篇文章,我们将通过自定义JDBC相关的底层组件,来实现数据的自动加解密功能,从而无需在业务代码中进行任何相关的修改。
通过自定义JDBC底层的组件方案,我们可以实现数据的加解密功能,而无需特别关注具体项目中采用的是哪一种持久层框架。
首先,我们先加入下面的依赖
<dependency><groupId>com.github.jsqlparser</groupId><artifactId>jsqlparser</artifactId><version>5.1</version></dependency><dependency><groupId>com.manticore-projects.jsqlformatter</groupId><artifactId>jsqlformatter</artifactId><version>5.0</version></dependency>
我们将通过jsqlparser进行SQL解析处理。
如果你对jsqlparser还不熟悉,请查看下面这篇文章:
2.1 自定义数据源
public class PackDataSource extends HikariDataSource {public Connection getConnection() throws SQLException {Connection connection = super.getConnection();return new EncryptConnection(connection) ;}}
为了保留Hikari数据源的强大功能,所以这里我们直接继承HikariDataSource数据源。这里为了简单起见,我们仅仅重写了父类的getConnection方法(获取数据库连接)。
配置数据源
public class DataSourceConfig {PackDataSource dataSource(DataSourceProperties properties) {return (PackDataSource) DataSourceBuilder.create().type(PackDataSource.class).driverClassName(properties.getDriverClassName()).url(properties.getUrl()).username(properties.getUsername()).password(properties.getPassword()).build() ;}}
兼容默认Hikari数据源的所有配置。
2.2 自定义Connection
public class EncryptConnection extends AbstractConnection {public EncryptConnection(Connection delegate) {super(delegate);}public PreparedStatement prepareStatement(String sql) throws SQLException {PreparedStatement statement = super.prepareStatement(sql) ;// 解析SQLExecuteSqlInfo executeSqlInfo = SqlParserUtils.parse(sql) ;return new EncryptPreparedStatement(statement, executeSqlInfo) ;}public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {PreparedStatement statement = super.prepareStatement(sql, autoGeneratedKeys) ;// 解析SQLExecuteSqlInfo executeSqlInfo = SqlParserUtils.parse(sql) ;return new EncryptPreparedStatement(statement, executeSqlInfo) ;}public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)throws SQLException {PreparedStatement statement = super.prepareStatement(sql, resultSetType, resultSetConcurrency);// 解析SQLExecuteSqlInfo executeSqlInfo = SqlParserUtils.parse(sql) ;return new EncryptPreparedStatement(statement, executeSqlInfo) ;}}
由于篇幅的原因,我们这里这里仅仅对部分的方法进行了重写。在获取Statement对象时,我们对当前要执行的SQL语句进行解析,主要是读取当前SQL操作什么表,有哪些字段。在下面会将该解析SQL的工具类贴出。
2.3 自定义PreparedStatement对象
public class EncryptPreparedStatement extends AbstractPreparedStatement {private final ExecuteSqlInfo executeSqlInfo ;public EncryptPreparedStatement(PreparedStatement delegate, ExecuteSqlInfo executeSqlInfo) {super(delegate);this.executeSqlInfo = executeSqlInfo ;}public void setString(int parameterIndex, String x) throws SQLException {String table = executeSqlInfo.getTable() ;List<String> columns = CryptoUtils.getEncryptProperties().getTables().get(table) ;if (columns != null) {ColumnInfo columnInfo = executeSqlInfo.getColumnInfo(parameterIndex) ;if (columnInfo != null && columns.contains(columnInfo.column)) {x = CryptoUtils.encrypt(x) ;}}super.setString(parameterIndex, x);}public ResultSet executeQuery() throws SQLException {ResultSet resultSet = super.executeQuery();return new EncryptResultSet(resultSet, this.executeSqlInfo) ;}public ResultSet getResultSet() throws SQLException {ResultSet resultSet = super.getResultSet();return new EncryptResultSet(resultSet, this.executeSqlInfo) ;}}
在该类中我们也仅仅是重写了部分核心的方法。如下方法说明:
setString:该方法中我们将会对哪些需要加密的列进行加密处理


