MyBatisPlus data automatic encryption and decryption access and field tamper-proof validity check code automatic generation storage processor

The overall principle: use the Mybatis interceptor to intercept ResultSetHandler, and perform attribute decryption and integrity proofreading. Replace the default ParameterHandler handler. Do attribute encrypted storage and integrity encrypted storage.
The code structure is as follows:

Explanation of each function class:
1. EntityClassResolver: used to resolve the Entity parameters of the current MapperStatament.
2. EntityValueHelper: Get or set the attribute value tool of Entity object.
3. SecretConfigurationCustomizer: Custom configuration that makes SecretMybatisXMLLanguageDriver effective. Replace the default XMLLanguageDriver of mybatisPlus
4. SecretMybatisXMLLanguageDriver: A custom configuration that makes SecretMybatisParameterHandler effective. Replace the default MybatisParameterHandler of mybatisPlus
5. SecretDecryptInterceptor intercepts ResultSetHandler.handleResultSets and decrypts the entity attribute with the SecretField field.
6. SecretField, mark the field notes that require encryption, decryption and integrity verification.
7. SecretModel, marking that the entity has fields that need to be encrypted and decrypted.
8. SecretProvider, encryption and decryption password provider.
9. SecretProviders, encryption and decryption provider tool class.
10. SeretSecurityAutoConfiguration, the general configuration class, is used to enable whether to register with Spring to enable encryption components.
SecretWrapper, the encapsulation class for temporary variables related to the encryption and decryption process.
SecretWrarpperEnhancer: An extension interface for low-level code to set some encryption and decryption fields to the entity object of high-level code.
SecretWrapperEnhancers, extended interface tool class.

Several key classes:

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) {<!-- -->
        /* Use a custom 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 {<!-- -->
        // Get the type of the result set
        MappedStatement mappedStatement = resolveMappedStatement(invocation);
        String id = mappedStatement. getId();
        //
        Boolean needDecrypt = EXISTS_DECRYPT. get(id);
        if (null != needDecrypt & amp; & amp; !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 & amp; & amp; 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) {<!-- -->
        // decrypt
        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);
            }
        }

        // integrity check
        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)) {<!-- -->
                // Find the signature of the current property
                String signCodeField = signCodeFieldContainer. get(signField);
                Object signCodeValue = ZYBeanUtils. getProperty(data, signCodeField);
                if (ZYStrUtils.isNotNull(signCodeValue)) {<!-- -->
                    // check data integrity
                    boolean legal = SecretProviders.isLegal(String.valueOf(fieldValue), String.valueOf(signCodeValue));
                    if (!legal) {<!-- -->
                        throw new LocalException("Illegal data" + 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;
    }

    /**
     * Batch (populate primary key ID)
     *
     * @param ms MappedStatement
     * @param parameterObject insert database object
     * @return ignore
     */
    protected static Object processBatch(MappedStatement ms, Object parameterObject) {<!-- -->
        // check parameterObject
        if (null == parameterObject) {<!-- -->
            return null;
        }
        // Whether the global configuration configures the filler
        MetaObjectHandler metaObjectHandler = GlobalConfigUtils.getMetaObjectHandler(ms.getConfiguration());
        boolean isFill = false;
        boolean isInsert = false;
        /* Only handle insert or update operations */
        if (ms.getSqlCommandType() == SqlCommandType.INSERT) {<!-- -->
            isFill = true;
            isInsert = true;
        } else if (ms.getSqlCommandType() == SqlCommandType.UPDATE & amp; & amp;
                metaObjectHandler != null & amp; & amp; 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 {<!-- -->
                        /*
                         * Non-table mapping classes are not processed
                         */
                        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;
    }

    /**
     * Handle normal batch insert logic
     * <p>
     * org.apache.ibatis.session.defaults.DefaultSqlSession$StrictMap This class method
     * wrapCollection implements StrictMap encapsulation logic
     *</p>
     *
     * @param parameter insert database object
     * @return
     */
    @SuppressWarnings({<!-- -->"rawtypes", "unchecked"})
    protected static Collection<Object> getParameters(Object parameter) {<!-- -->
        Collection<Object> parameters = null;
        if (parameter instanceof Collection) {<!-- -->
            parameters = (Collection) parameters;
        } 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;
    }

    /**
     * Custom meta object populate controller
     *
     * @param metaObjectHandler metadata filling processor
     * @param tableInfo database table reflection information
     * @param ms MappedStatement
     * @param parameterObject insert database object
     * @return Object
     */
    protected static Object populateKeys(MetaObjectHandler metaObjectHandler, TableInfo tableInfo,
                                         MappedStatement ms, Object parameterObject, boolean isInsert) {<!-- -->
        if (null == tableInfo) {<!-- -->
            /* do not process */
            return parameterObject;
        }
        /* custom meta object populate controller */
        MetaObject metaObject = ms.getConfiguration().newMetaObject(parameterObject);
        // populate the primary key
        if (isInsert & amp; & amp; !StringUtils. isEmpty(tableInfo. getKeyProperty())
                 & amp; & amp; null != tableInfo.getIdType() & amp; & amp; tableInfo.getIdType().getKey() >= 3) {<!-- -->
            Object idValue = metaObject.getValue(tableInfo.getKeyProperty());
            /* Custom 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 & amp; & amp; metaObjectHandler. openInsertFill()) {<!-- -->
                // insert padding
                metaObjectHandler.insertFill(metaObject);
            } else if (!isInsert) {<!-- -->
                // update padding
                metaObjectHandler. updateFill(metaObject);
            }
        }
        return metaObject. getOriginalObject();
    }

    // The value of the integrity signCode can be set in advance, and it can be set in advance
    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;
        }
        // Encrypt with integrity data field content
        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);
                // The original value of the object cannot be destroyed, so put this to handle the final setting sql data
                value = doEncryptIfNecessary(metaObject, propertyName, value);
            }
            TypeHandler typeHandler = parameterMapping. getTypeHandler();
            JdbcType jdbcType = parameterMapping. getJdbcType();
            if (value == null & amp; & amp; 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);
            }
        }
    }

    // decrypt object
    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);
        // add a password
        SecretModel secretField = secretWrapper. getSecretField(propertyTokenizer);
        if (null != secretField & amp; & amp; secretField.isNeedEncrypt()) {<!-- -->
            return SecretProviders.encrypt(String.valueOf(value));
        }
        return value;
    }

    private boolean isNecessarySecret(MetaObject metaObject, Object value) {<!-- -->
        SqlCommandType sqlCommandType = mappedStatement. getSqlCommandType();
        // not adding or modifying
        if (!sqlCommandType.equals(SqlCommandType.INSERT) & amp; & amp; !sqlCommandType.equals(SqlCommandType.UPDATE)) {<!-- -->
            return false;
        }

        if (ZYStrUtils.isNull(value)) {<!-- -->
            return false;
        }
        // Only support processing string type
        return value instanceof String;

    }
}