整体原理:用Mybatis拦截器拦截ResultSetHandler,做属性解密和完整性校奏。替换默认的ParameterHandler处理器。做属性加密存储和完整性加密存储。
代码结构如下:
各功能类解释:
1、EntityClassResolver:用于解析当前MapperStatament的Entity参数。
2、EntityValueHelper: 获取或设置Entity对象的属性值工具。
3、SecretConfigurationCustomizer:使得SecretMybatisXMLLanguageDriver生效的自定义配置。替换mybatisPlus默认的XMLLanguageDriver
4、SecretMybatisXMLLanguageDriver:使得SecretMybatisParameterHandler生效的自定义配置。替换mybatisPlus默认的MybatisParameterHandler
5、SecretDecryptInterceptor,拦截ResultSetHandler.handleResultSets,解密带SecretField字段的entity属性。
6、SecretField,标记需要加解密和完整性校验的字段注解。
7、SecretModel,标记该实体有需要加解密的字段。
8、SecretProvider,加解密码供应商。
9、SecretProviders,加解密供应商工具类。
10、SeretSecurityAutoConfiguration,总配置类,用于开启是否向Spring注册启用加密码组件。
SecretWrapper,加解密过程相关临时变量封装类。
SecretWrarpperEnhancer:低层次代码向高层次代码的entity对象设置一些加解密字段的扩展接口。
SecretWrapperEnhancers,扩展接口工具类。
几个关键类:
public class SecretConfigurationCustomizer implements ConfigurationCustomizer {
@Override
public void customize(Configuration configuration) {
LanguageDriverRegistry languageRegistry = configuration.getLanguageRegistry();
languageRegistry.setDefaultDriverClass(SecretMybatisXMLLanguageDriver.class);
}
}
public class SecretMybatisXMLLanguageDriver extends XMLLanguageDriver {
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
/* 使用自定义 ParameterHandler */
return new SecretMybatisParameterHandler(mappedStatement, parameterObject, boundSql);
}
}
@Intercepts({
@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)
})
public class SecretDecryptInterceptor implements Interceptor {
private final static Map<String, Boolean> EXISTS_DECRYPT = new HashMap<>();
private final static Map<String, SecretWrapper> NEED_DECRYPT_FIELDS = new HashMap<>();
@Autowired
private SecretProvider secretProvider;
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 获取结果集的类型
MappedStatement mappedStatement = resolveMappedStatement(invocation);
String id = mappedStatement.getId();
//
Boolean needDecrypt = EXISTS_DECRYPT.get(id);
if (null != needDecrypt && !needDecrypt) {
return invocation.proceed();
}
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
if (ZYListUtils.isEmptyList(resultMaps)) {
EXISTS_DECRYPT.put(id, false);
return invocation.proceed();
}
SecretWrapper secretWrapper = NEED_DECRYPT_FIELDS.get(id);
if (null == secretWrapper) {
Class<?> resultType = resultMaps.get(0).getType();
secretWrapper = new SecretWrapper(resultType);
if (secretWrapper.isEmpty()) {
EXISTS_DECRYPT.put(id, false);
return invocation.proceed();
} else {
EXISTS_DECRYPT.put(id, true);
NEED_DECRYPT_FIELDS.put(id, secretWrapper);
}
}
Object resultObject = invocation.proceed();
if (null != resultObject && resultObject instanceof List) {
List<?> list = (List<?>) resultObject;
for (Object item : list) {
this.doDecryptObjectValue(item, secretWrapper);
}
}
return resultObject;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
private MappedStatement resolveMappedStatement(Invocation invocation) {
DefaultResultSetHandler defaultResultSetHandler = (DefaultResultSetHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(defaultResultSetHandler);
return (MappedStatement) metaObject.getValue("mappedStatement");
}
protected void doDecryptObjectValue(Object data, SecretWrapper secretWrapper) {
// 解密
List<String> decryptFields = secretWrapper.getDecryptFields();
for (String field : decryptFields) {
Object fieldValue = ZYBeanUtils.getProperty(data, field);
if (ZYStrUtils.isNotNull(fieldValue)) {
fieldValue = SecretProviders.decrypt(String.valueOf(fieldValue));
ZYBeanUtils.setProperty(data, field, fieldValue);
}
}
// 完整性校验
List<String> signFields = secretWrapper.getSignFields();
Map<String, String> signCodeFieldContainer = secretWrapper.getSignCodeFieldContainer();
for (String signField : signFields) {
Object fieldValue = ZYBeanUtils.getProperty(data, signField);
if (ZYStrUtils.isNotNull(fieldValue)) {
// 找到当前属性的签名
String signCodeField = signCodeFieldContainer.get(signField);
Object signCodeValue = ZYBeanUtils.getProperty(data, signCodeField);
if (ZYStrUtils.isNotNull(signCodeValue)) {
// 校验数据完整性
boolean legal = SecretProviders.isLegal(String.valueOf(fieldValue), String.valueOf(signCodeValue));
if (!legal) {
throw new LocalException("非法数据" + fieldValue);
}
}
}
}
}
}
public class SecretMybatisParameterHandler extends DefaultParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public SecretMybatisParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
super(mappedStatement, processBatch(mappedStatement, parameterObject), boundSql);
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
/**
* 批量(填充主键 ID)
*
* @param ms MappedStatement
* @param parameterObject 插入数据库对象
* @return ignore
*/
protected static Object processBatch(MappedStatement ms, Object parameterObject) {
//检查 parameterObject
if (null == parameterObject) {
return null;
}
// 全局配置是否配置填充器
MetaObjectHandler metaObjectHandler = GlobalConfigUtils.getMetaObjectHandler(ms.getConfiguration());
boolean isFill = false;
boolean isInsert = false;
/* 只处理插入或更新操作 */
if (ms.getSqlCommandType() == SqlCommandType.INSERT) {
isFill = true;
isInsert = true;
} else if (ms.getSqlCommandType() == SqlCommandType.UPDATE &&
metaObjectHandler != null && metaObjectHandler.openUpdateFill()) {
isFill = true;
}
if (isFill) {
Collection<Object> parameters = getParameters(parameterObject);
if (null != parameters) {
List<Object> objList = new ArrayList<>();
for (Object parameter : parameters) {
TableInfo tableInfo = TableInfoHelper.getTableInfo(parameter.getClass());
if (null != tableInfo) {
objList.add(populateKeys(metaObjectHandler, tableInfo, ms, parameter, isInsert));
} else {
/*
* 非表映射类不处理
*/
objList.add(parameter);
}
}
return objList;
} else {
TableInfo tableInfo = null;
if (parameterObject instanceof Map) {
Map map = (Map) parameterObject;
if (map.containsKey(Constants.ENTITY)) {
Object et = map.get(Constants.ENTITY);
if (et != null) {
if (et instanceof Map) {
Map realEtMap = (Map) et;
if (realEtMap.containsKey("MP_OPTLOCK_ET_ORIGINAL")) {
//refer to OptimisticLockerInterceptor.MP_OPTLOCK_ET_ORIGINAL
tableInfo = TableInfoHelper.getTableInfo(realEtMap.get("MP_OPTLOCK_ET_ORIGINAL").getClass());
}
} else {
tableInfo = TableInfoHelper.getTableInfo(et.getClass());
}
}
}
} else {
tableInfo = TableInfoHelper.getTableInfo(parameterObject.getClass());
}
return populateKeys(metaObjectHandler, tableInfo, ms, parameterObject, isInsert);
}
}
return parameterObject;
}
/**
* 处理正常批量插入逻辑
*
* org.apache.ibatis.session.defaults.DefaultSqlSession$StrictMap 该类方法
* wrapCollection 实现 StrictMap 封装逻辑
*
*
* @param parameter 插入数据库对象
* @return
*/
@SuppressWarnings({"rawtypes", "unchecked"})
protected static Collection<Object> getParameters(Object parameter) {
Collection<Object> parameters = null;
if (parameter instanceof Collection) {
parameters = (Collection) parameter;
} else if (parameter instanceof Map) {
Map parameterMap = (Map) parameter;
if (parameterMap.containsKey("collection")) {
parameters = (Collection) parameterMap.get("collection");
} else if (parameterMap.containsKey("list")) {
parameters = (List) parameterMap.get("list");
} else if (parameterMap.containsKey("array")) {
parameters = Arrays.asList((Object[]) parameterMap.get("array"));
}
}
return parameters;
}
/**
* 自定义元对象填充控制器
*
* @param metaObjectHandler 元数据填充处理器
* @param tableInfo 数据库表反射信息
* @param ms MappedStatement
* @param parameterObject 插入数据库对象
* @return Object
*/
protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
MappedStatement ms, Object parameterObject, boolean isInsert) {
if (null == tableInfo) {
/* 不处理 */
return parameterObject;
}
/* 自定义元对象填充控制器 */
MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
// 填充主键
if (isInsert && !StringUtils.isEmpty(tableInfo.getKeyProperty())
&& null != tableInfo.getIdType() && tableInfo.getIdType().getKey() >= 3) {
Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
/* 自定义 ID */
if (StringUtils.checkValNull(idValue)) {
if (tableInfo.getIdType() == IdType.ID_WORKER) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getId());
} else if (tableInfo.getIdType() == IdType.ID_WORKER_STR) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.getIdStr());
} else if (tableInfo.getIdType() == IdType.UUID) {
metaObject.setValue(tableInfo.getKeyProperty(), IdWorker.get32UUID());
}
}
}
doCompleteSignCode(metaObject, tableInfo);
if (metaObjectHandler != null) {
if (isInsert && metaObjectHandler.openInsertFill()) {
// 插入填充
metaObjectHandler.insertFill(metaObject);
} else if (!isInsert) {
// 更新填充
metaObjectHandler.updateFill(metaObject);
}
}
return metaObject.getOriginalObject();
}
// 可提前设置完整性signCode的值,所放提前设置
private static void doCompleteSignCode(MetaObject metaObject, TableInfo tableInfo) {
Class<?> clazz = tableInfo.getClazz();
SecretWrapper secretWrapper = new SecretWrapper(clazz);
if (secretWrapper.isEmpty()) {
return;
}
Map<String, String> signContentContainer = secretWrapper.getSignContentContainer();
signContentContainer.forEach((signCodeField, signContentField) -> {
setSignCodeValue(metaObject, signCodeField, signContentField);
});
}
private static void setSignCodeValue(MetaObject metaObject, String signCodeField, String signContentField) {
if (ZYStrUtils.isNull(signContentField)) {
return;
}
Object signContent = EntityValueHelper.getProperties(metaObject, signContentField);
if (ZYStrUtils.isNull(signContent)) {
return;
}
// 用完整性数据字段内容加密
String signCode = SecretProviders.genLegalSign(String.valueOf(signContent));
if (ZYStrUtils.isNotNull(signCode)) {
EntityValueHelper.setProperties(metaObject, signCodeField, signCode);
}
}
@Override
@SuppressWarnings("unchecked")
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (ZYListUtils.isEmptyList(parameterMappings)) {
return;
}
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() == ParameterMode.OUT) {
continue;
}
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
// 不能破坏对象原来的值,所以放这处理最终的设置sql数据
value = doEncryptIfNecessary(metaObject, propertyName, value);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
// 解密对象
private Object doEncryptIfNecessary(MetaObject metaObject, String propertyName, Object value) {
if (!isNecessarySecret(metaObject, value)) {
return value;
}
Class<?> moduleClass = EntityClassResolver.resolveClass(mappedStatement, parameterObject);
if (null == moduleClass) {
return value;
}
SecretWrapper secretWrapper = new SecretWrapper(moduleClass);
if (secretWrapper.isEmpty()) {
return value;
}
PropertyTokenizer propertyTokenizer = new PropertyTokenizer(propertyName);
// 加个密
SecretModel secretField = secretWrapper.getSecretField(propertyTokenizer);
if (null != secretField && secretField.isNeedEncrypt()) {
return SecretProviders.encrypt(String.valueOf(value));
}
return value;
}
private boolean isNecessarySecret(MetaObject metaObject, Object value) {
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
// 不是添加或修改
if (!sqlCommandType.equals(SqlCommandType.INSERT) && !sqlCommandType.equals(SqlCommandType.UPDATE)) {
return false;
}
if (ZYStrUtils.isNull(value)) {
return false;
}
// 只支持处理string类型
return value instanceof String;
}
}