In the SpringBoot project, the user-defined annotation + interceptor gracefully realizes the encryption and decryption of sensitive data!

Posted by keithschm on Tue, 08 Mar 2022 05:30:58 +0100

 

 

In actual production projects, it is often necessary to encrypt and store sensitive data such as ID card information, mobile phone number, real name, etc. in the database, but manual encryption and decryption of sensitive information in the business code is not elegant, and there may even be wrong encryption, missing encryption, and business personnel need to know the actual encryption rules.

This article will introduce the detailed process of intercepting and encrypting sensitive data before storage in the form of springboot+mybatis interceptor + custom annotation.

1, What is Mybatis Plugin

In the official document of mybatis, the introduction of Mybatis plugin is as follows:

MyBatis allows you to intercept calls at some point during the execution of mapped statements. By default, MyBatis allows the use of plug-ins to intercept method calls, including:

//Statement execution interception
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

// Intercept during parameter acquisition and setting
ParameterHandler (getParameterObject, setParameters)

// Intercept the returned results
ResultSetHandler (handleResultSets, handleOutputParameters)

//sql statement interception
StatementHandler (prepare, parameterize, batch, update, query)

In short, in the whole cycle of executing sql, we can cut into a certain point to deal with the parameters of sql, the result set of sql execution, the sql statement itself and so on. Based on this feature, we can use it to uniformly encrypt the data we need to encrypt (this is how the paging plug-in pageHelper implements the database paging query).

2, Implement the encryption and decryption interceptor of sensitive information based on annotation

2.1 implementation ideas

For data encryption and decryption, there should be two interceptors to intercept the data

Refer to the official documents, so here we should use the ParameterHandler interceptor to encrypt the incoming parameters

Decrypt the output parameter using the ResultSetHandler interceptor.The fields to be encrypted and decrypted by the target may need to be changed flexibly. At this time, we define an annotation to annotate the fields to be encrypted, so we can cooperate with the interceptor to encrypt and decrypt the required data.

The interceptor interface of mybatis has the following methods to be implemented.

public interface Interceptor {
 
  //Main parameters interception method
  Object intercept(Invocation invocation) throws Throwable;
 
  //mybatis plug-in chain
  default Object plugin(Object target) {return Plugin.wrap(target, this);}
 
  //Custom plug-in profile method
  default void setProperties(Properties properties) {}
 
}

2.2 notes for defining sensitive information to be encrypted and decrypted

Define annotations for sensitive information classes (such as entity class POJOPO)

/**
 * Annotation of sensitive information class
 */
@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveData {
}

Annotation for defining sensitive fields in sensitive information class

/**
 * Annotation of sensitive fields in sensitive information class
 */
@Inherited
@Target({ ElementType.Field })
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
}

2.3 define encryption interface and its implementation class

Define the encryption interface to facilitate the expansion of encryption methods in the future (for example, the expansion of AES encryption algorithm supports PBE algorithm, which only needs to be specified during injection)

public interface EncryptUtil {
    
    /**
     * encryption
     *
     * @param declaredFields paramsObject Declared fields
     * @param paramsObject   mapper Instance of paramtype in
     * @return T
     * @throws IllegalAccessException Field inaccessible exception
     */
     <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException;
}

EncryptUtil's AES encryption implementation class. Here AESUtil is a self encapsulated AES encryption tool, which can be encapsulated by small partners. It is not provided in this article. (search for official account number Java, reply, "2021", send you a Java interview question treasure).

@Component
public class AESEncrypt implements EncryptUtil {
    
    @Autowired
    AESUtil aesUtil;
 
    /**
     * encryption
     *
     * @param declaredFields paramsObject Declared fields
     * @param paramsObject   mapper Instance of paramtype in
     * @return T
     * @throws IllegalAccessException Field inaccessible exception
     */
    @Override
    public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            //Retrieve all fields annotated by EncryptDecryptField
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                //For the time being, only String type encryption is implemented
                if (object instanceof String) {
                    String value = (String) object;
                    //Encryption here I use the custom AES encryption tool
                    field.set(paramsObject, aesUtil.encrypt(value));
                }
            }
        }
        return paramsObject;
    }
}

2.4 implementation of incoming secret interceptor

Org. In Myabtis package apache. ibatis. plugin. The interceptor interceptor interface requires us to implement the following three methods

public interface Interceptor {
 
  //Core interception logic
  Object intercept(Invocation invocation) throws Throwable;
  
  //Interceptor chain 
  default Object plugin(Object target) {return Plugin.wrap(target, this);}
 
  //Custom profile actions
  default void setProperties(Properties properties) { }
 
}

Therefore, referring to the example in the official document, we customize an incoming secret interceptor.

@The Intercepts annotation enables the interceptor, and the @ Signature annotation defines the actual type of the interceptor.

@In Signature

The type attribute specifies that the current interceptor uses StatementHandler, ResultSetHandler and ParameterHandler. A method attribute of the Executor specifies the specific methods of the above four types (you can view their methods inside the class). The args attribute specifies the precompiled statement. Here we use ParameterHandler Setparamters() method, intercepting mapper Instance of paramsType in XML (that is, execute the interceptor in every mapper statement containing paramsType attribute to intercept the instance of paramsType)

/**
 * Encryption interceptor
 * Note that the @ Component annotation must be added
 *
 * @author : tanzj
 * @date : 2020/1/19.
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor implements Interceptor {
 
    private final EncryptDecryptUtil encryptUtil;
 
    @Autowired
    public EncryptInterceptor(EncryptDecryptUtil encryptUtil) {
        this.encryptUtil = encryptUtil;
    }
 
    @Override
   
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //@After type= parameterHandler is specified in the Signature, the invocation here Gettarget () is parameterHandler 
        //If ResultSetHandler is specified, it can be forcibly converted to ResultSetHandler here
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // Get the parameter object, that is, the instance of paramsType in mapper
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);
        //Take out the instance
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            Class<?> parameterObjectClass = parameterObject.getClass();
            //Verify whether the class of this instance is annotated by @ SensitiveData
            SensitiveData sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveData.class);
            if (Objects.nonNull(sensitiveData)) {
                //Take out all fields of the current class and pass in the encryption method
                Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                encryptUtil.encrypt(declaredFields, parameterObject);
            }
        }
        return invocation.proceed();
    }
 
    /**
     * Remember to configure, otherwise the current interceptor will not join the interceptor chain
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
 
    //Write custom configuration. If there is no custom configuration, this method can be directly blank
    @Override
    public void setProperties(Properties properties) {
    }
}

This completes the custom encryption interception encryption.

2.5 define decryption interface and its implementation class

Decrypt the interface, where result is mapper An instance of resultType in XML.

public interface DecryptUtil {
 
    /**
     * decrypt
     *
     * @param result resultType Examples of
     * @return T
     * @throws IllegalAccessException Field inaccessible exception
     */
     <T> T decrypt(T result) throws IllegalAccessException;
    
}

Decryption interface AES tool decryption implementation class

public class AESDecrypt implements DecryptUtil {
    
    @Autowired
    AESUtil aesUtil;
    
    /**
     * decrypt
     *
     * @param result resultType Examples of
     * @return T
     * @throws IllegalAccessException Field inaccessible exception
     */
    @Override
    public <T> T decrypt(T result) throws IllegalAccessException {
        //Get the class of resultType
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //Retrieve all fields annotated by EncryptDecryptField
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);
                //Only decryption of String is supported
                if (object instanceof String) {
                    String value = (String) object;
                    //Decrypt the annotated fields one by one
                    field.set(result, aesUtil.decrypt(value));
                }
            }
        }
        return result;
    }
}

2.6 define parameter decryption interceptor

@Slf4j
@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor implements Interceptor {
 
    @Autowired
    DecryptUtil aesDecrypt;
 
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //Retrieve the results of the query
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //selectList based
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    //Decrypt one by one
                    aesDecrypt.decrypt(result);
                }
            }
        //Based on selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                aesDecrypt.decrypt(resultObject);
            }
        }
        return resultObject;
    }
 
    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        SensitiveData sensitiveData = AnnotationUtils.findAnnotation(objectClass, SensitiveData.class);
        return Objects.nonNull(sensitiveData);
    }
 
 
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties(Properties properties) {
 
    }
}

So far, the configuration of decryption interceptor is completed.

3. Annotate the fields to be encrypted and decrypted in the entity class

At this time, specify paramType=User resultType=User in mapper to realize the encryption and decryption operation based on mybatis interceptor.

 

END

 

Previous recommendation

 

In the future, APP is all for one person

A programmer gets an offer and doubles his salary. Do you want to go? Netizen: programming for Yama

Using the "picture and text voice to text" to earn 10000 + a month, a regular and easy to operate Xiaobai can easily earn tens of thousands a month