需求
贩卖用户数据都快成为行业传统了。为了加强保护用户个人信息,需要对数据库中记录个人信息的字段进行加密。 即入库时自动加密,查询数据自动解密。
方案
- 定义TypeHandler, 需要加解密的字段指定TypeHandler。
- DTO层手动处理。
- 编写拦截器插件,定义加密注解,监听
ParameterHandler
,ResultSetHandler
信号对带加密注解的参数进行加密,带加密注解的字段解密。 (尝试过,存在不能容忍的弊端,例如插入对象后,需要加密字段的值被加密,后续代码要使用还得解密。 不推荐使用)
- sql语句修改,调用mysql提供的函数加解密
本文选用第一种。
Code
定义字段加解密接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public interface StringEncryptor {
String encrypt(String str);
String decrypt(String encryptedStr);
}
|
定义TypeHandler
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
|
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); }
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; }
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
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
|
@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:
1
| <result column="email" jdbcType="VARCHAR" property="email" typeHandler="secretStringTypeHandler"/>
|
参数:
1
| <select> xxx where email = #{email, typeHandler=secretStringTypeHandler}</select>
|
需要注意:
- 实现
StringEncryptor
的加密器应选用幂等的加密算法。
- 加密后的字段数据长度将变长。数据结构需修改。
- 加密字段使用不了模糊查询