This is the 14th day of my participation in the First Challenge 2022

background

Is more sensitive in some application scenarios, there may be a part of the field of database encryption processing, but by what means can be unified good encryption processing, make business call layer can reduce the attention to field encryption, that is to say, if do united drew encryption makes calls to business logic layer non-inductive?

Analysis of the

Starting from the data acquisition method of Mybatis, after the support of MyBatis – Plus, there should be three ways to obtain data.

  • throughmybatis-plusExtended generic interfaceServiceImpl, encapsulates some general add, delete, change and check interface.
  • throughmybatis-plustheLambdaQueryWrapperObtain data by manually compiling query conditions.
  • By writing concrete isSQLThe script toxxx.xmlFile, to obtain data.

Therefore, if it is necessary to make the business end insensitive to the encryption and decryption of some fields, the extracted structure should possibly cover the above three methods.

No matter how database fields are encrypted or decrypted, the first step is to define an annotation to mark which fields in an entity need to be encrypted or decrypted. Here, the place used is temporarily screened, and the specific marking points need to be combined with the corresponding processing means.

//@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataEncrypt {
    String key(a) default "";
}
Copy the code

Methods a

The most simple and crude way is through AOP, directly to the mapper all xxxMapper file to intercept under the package, and then through the reflection of operating entity attribute traversal, obtain the corresponding markup annotations, to determine whether to encrypt, and after the return data, also to use reflection to decrypt the response result list data.



@Slf4j
@Aspect
@Component
public class JpaDataEncryptAspect {

    @Value("${key:ABCDEFGHIJKLMN}")
    private String defaultKey;

    /** ** cut point */
    @Pointcut("execution(public * com.cn.xiaocainiaoya.. mapper.. *Mapper.*(..) )"
    public void pointCut(a) {}@Around("pointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // Encrypt parameters before execution
        for (Object arg : point.getArgs()) {
            dataEncrypt(arg);
        }
        // Execute method
        Object result = point.proceed();
        // Decrypt the parameters
        for (Object arg : point.getArgs()) {
            dataDesEncrypt(arg);
        }
        // Decrypt the result after execution
        if (result instanceof List) {
            List<Object> tmps = new ArrayList<>();
            List list = ((List) result);
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Object obj = it.next();
                / / remove first
                it.remove();
                tmps.add(dataDesEncrypt(obj));
            }
            list.addAll(tmps);
            return list;
        } else if (result instanceof IPage) {
            List<Object> tmps = new ArrayList<>();
            List list = ((IPage) result).getRecords();
            Iterator it = list.iterator();
            while (it.hasNext()) {
                Object obj = it.next();
                / / remove first
                it.remove();
                tmps.add(dataDesEncrypt(obj));
            }
            list.addAll(tmps);
            ((IPage) result).setRecords(list);
            return result;
        } else {
            returndataDesEncrypt(result); }}}Copy the code

** Note: there are two points to note on ** processing: 1. Since the parameters may be used by the business layer after the data acquisition processing, the data needs to be decrypted as original after the execution of the specific target method. 2. The corresponding List data also needs to be processed.

Way 2

Through the open extension interface of Mybatis, write the corresponding plug-in to intercept, and conduct data encryption and decryption in a unified manner. Through plug-in processing, it is necessary to understand some internal structure of Mybatis, here intercepts the link of setting parameters of Mybatis, and then carries on corresponding encryption and decryption processing of parameters. (Only the encryption logic for parameter processing is shown here, not the decryption logic for the result set.)


@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),})
@Component
@Slf4j
public class ParameterInterceptor implements Interceptor {

    private final static String RECORD = "record";

    private final static String EXAMPLE = "example";

    /** * Interceptor handles interface **@Author: xiaocainiaoya
     * @Date: 2020/6/11
     * @param invocation
     * @return: java.lang.Object
     **/
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // Get the parameter object, which is an instance of paramsType in mapper
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        Object parameterObject = parameterField.get(parameterHandler);

        Field boundSqlField = parameterHandler.getClass().getDeclaredField("boundSql");
        boundSqlField.setAccessible(true);
        BoundSql boundSql = (BoundSql) boundSqlField.get(parameterHandler);

        Field mappedStatement = parameterHandler.getClass().getDeclaredField("mappedStatement");
        mappedStatement.setAccessible(true);
        //MappedStatement ms = (MappedStatement)mappedStatement.get(parameterHandler);
        // how XML writes SQL scripts or example's update operation
        if(parameterObject instanceof MapperMethod.ParamMap){
            MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap)parameterObject;
            // Update with example
            Object updateEntity = null;
            Object example = null;
            try{
                updateEntity = paramMap.get(RECORD);
                example = paramMap.get(EXAMPLE);
            }catch(Exception e){
                SQL > select * from example; // Select * from example
                return invocation.proceed();
            }
            if(ObjectUtil.isNotNull(updateEntity) && ObjectUtil.isNotNull(example)){
                // The modified entity
                Field[] fields = ConvertUtils.getAllFields(updateEntity);
                for(Field field : fields){
                    JpaDataEncrypt jpaDataEncrypt = field.getAnnotation(JpaDataEncrypt.class);
                    if(ObjectUtil.isNull(jpaDataEncrypt)){
                        continue;
                    }
                    field.setAccessible(true);
                    Object value = field.get(updateEntity);
                    if(! (valueinstanceof String) || StringUtils.isEmpty((String)value)){
                        continue;
                    }
                    field.set(updateEntity, Sm4DecUtils.encrypt(strValue));
                }
                // Modify the condition exampleencryptExampleInfo((Example) example, boundSql); }}else if(parameterObject instanceof Example){
            // Use example to query
            encryptExampleInfo((Example)parameterObject, boundSql);
        }else{
            // Entity query cannot determine the type
        }
        return invocation.proceed();
    }
 
    @Override
    public void setProperties(Properties properties) {}}Copy the code

For relatively single query elements or for passing a reference to an XML query file through a Map, the plug-in approach seems to be difficult to handle at this point, because the type retrieved here is also of Map type, and the annotated tag for that field cannot be retrieved. Therefore, in this way, data can only be encrypted by the business side, which may lead to incomplete extraction.

@Mapper
public interface UserInfoMapper extends CommonMapper<UserInfo> {
    List<UserInfo> queryUserInfo(@Param("queryParam") Map queryParam);
}
Copy the code

conclusion

Compare the above two methods:

Method 1 requires dynamic proxy for all packages under mapper, which is relatively simple to write, but it does not satisfy LambdaQueryWrapper queries. And it does not meet the scenario of map parameter passing in the third point.

Method 2: Through the extension mechanism opened by Mybatis, the corresponding parameter setting interception and result set interception need to be written, but it cannot meet the third point mentioned in the beginning.

Therefore, the above two methods have their own advantages and disadvantages, and cannot completely handle all scenarios. In fact, for the parameter transmission in map mode mentioned in the third point, neither of the two methods can effectively handle the parameter transmission, and only some conventions can be carried out on the business side to transfer parameters through the entity mode.