数据库指定字段加密-Mybatis TypeHandler
需求
贩卖用户数据都快成为行业传统了。为了加强保护用户个人信息,需要对数据库中记录个人信息的字段进行加密。 即入库时自动加密,查询数据自动解密。
方案
- 定义TypeHandler, 需要加解密的字段指定TypeHandler。
- DTO层手动处理。
- 编写拦截器插件,定义加密注解,监听
ParameterHandler
,ResultSetHandler
信号对带加密注解的参数进行加密,带加密注解的字段解密。 (尝试过,存在不能容忍的弊端,例如插入对象后,需要加密字段的值被加密,后续代码要使用还得解密。 不推荐使用) - sql语句修改,调用mysql提供的函数加解密
本文选用第一种。
Code
定义字段加解密接口
/**
* 字符加密器
* 相同参数,返回值应该幂等
*/
public interface StringEncryptor {
/**
* Encrypt the string.
*/
String encrypt(String str);
/**
* Decrypt the string.
*/
String decrypt(String encryptedStr);
}
定义TypeHandler
/**
* 字段加解密处理, 无法解密返回原值
* 需要在xml中指定TypeHandler
* 返回值ResultMap:
* <result column="" jdbcType="VARCHAR" property="" typeHandler="secretStringTypeHandler"/>
* 参数:
* <select> xxx where email = #{email, typeHandler=secretStringTypeHandler}</select>
*/
public class SecretStringTypeHandler implements TypeHandler<String> {
private static final Logger log = LoggerFactory.getLogger(SecretStringTypeHandler.class);
private StringEncryptor stringEncryptor;
public SecretStringTypeHandler() {
}
public SecretStringTypeHandler(StringEncryptor stringEncryptor) {
this.stringEncryptor = stringEncryptor;
}
public StringEncryptor getStringEncryptor() {
return stringEncryptor;
}
public void setStringEncryptor(StringEncryptor stringEncryptor) {
this.stringEncryptor = stringEncryptor;
}
@Override
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, getEncryptResult(parameter));
}
@Override
public String getResult(ResultSet rs, String columnName) throws SQLException {
return getDecryptResult(rs.getString(columnName));
}
@Override
public String getResult(ResultSet rs, int columnIndex) throws SQLException {
return getDecryptResult(rs.getString(columnIndex));
}
@Override
public String getResult(CallableStatement cs, int columnIndex) throws SQLException {
return cs.getString(columnIndex);
}
/**
* 加密字段
* @param result 原始值
* @return 加密值
*/
private String getEncryptResult(String result) {
if (StringUtils.isBlank(result)) {
return result;
}
try {
result = stringEncryptor.encrypt(result);
} catch (Exception e) {
log.error("set param fail: {}, {}", result, e.getMessage());
}
return result;
}
/**
* 解密字段
* @param result 加密值
* @return 原始值
*/
private String getDecryptResult(String result) {
if (StringUtils.isBlank(result)) {
return result;
}
try {
result = stringEncryptor.decrypt(result);
} catch (Exception e) {
log.error("get result fail: {}, {}", result, e.getMessage());
}
return result;
}
}
注册TypeHandler
/**
* Mybatis 自定义配置
*/
@org.springframework.context.annotation.Configuration
public class MybatisConfigurationCustomizer implements ConfigurationCustomizer {
private static final Logger log = LoggerFactory.getLogger(MybatisConfigurationCustomizer.class);
@Autowired
private SecretStringTypeHandler secretStringTypeHandler;
@Bean
public StringEncryptor stringEncryptor() {
// 密码应从配置里面获得
return new AesEncryptor("password");
}
@Bean
public SecretStringTypeHandler secretStringTypeHandler(StringEncryptor stringEncryptor) {
return new SecretStringTypeHandler(stringEncryptor);
}
@Override
public void customize(Configuration configuration) {
log.debug("<-- MybatisConfigurationCustomizer ");
configuration.getTypeAliasRegistry()
.registerAlias("secretStringTypeHandler", SecretStringTypeHandler.class);
configuration.getTypeHandlerRegistry()
.register(secretStringTypeHandler);
}
}
使用
返回值ResultMap:
<result column="email" jdbcType="VARCHAR" property="email" typeHandler="secretStringTypeHandler"/>
参数:
<select> xxx where email = #{email, typeHandler=secretStringTypeHandler}</select>
需要注意:
- 实现
StringEncryptor
的加密器应选用幂等的加密算法。 - 加密后的字段数据长度将变长。数据结构需修改。
- 加密字段使用不了模糊查询