In actual production projects, sensitive data such as ID card information, mobile phone number and real name often need to be encrypted and stored in the database. However, manual encryption and decryption of sensitive information in 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 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 sql execution, we can cut into a certain point to deal with the parameters of sql, the result set of sql execution, the sql statement itself, etc. 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 database paging query).
2, Implement annotation based encryption and decryption interceptor for sensitive information
2.1 implementation ideas
For data encryption and decryption, there should be two interceptors to intercept the data
Refer to the official documentation, so here we should use the ParameterHandler interceptor to encrypt the incoming parameters
Decrypt the output parameter using the ResultSetHandler interceptor.
The target fields to be encrypted and decrypted 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 implement.
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 POJO\PO)
/** * Annotation of sensitive information class */ @Inherited @Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface SensitiveData { }
Annotations that define sensitive fields in sensitive information classes
/** * 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.
@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 realize the entry and participation of the secret interceptor
The org.apache.ibatis.plugin.Interceptor interceptor interceptor interface in the Myabtis package 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, ParameterHandler, and one of the executors
-
The method attribute specifies the specific methods using the above four types (you can view their methods inside the class).
-
The args property specifies the precompiled statement
Here we use Parameterhandler. Setparameters() method to intercept the instance of paramsType in mapper.xml (that is, execute the interceptor in each 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 { //@Signature Specified type= parameterHandler After that, here invocation.getTarget() Is parameterhandler //If ResultSetHandler is specified , Here, it can be forcibly converted to ResultSetHandler ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget(); // Get the parameter object, i.e mapper in paramsType Examples of Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject"); parameterField.setAccessible(true); //Take out 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 the instance of resultType in mapper.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 { //Fetch query results Object resultObject = invocation.proceed(); if (Objects.isNull(resultObject)) { return null; } //Based on selectList 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) { } }
This completes the configuration of the decryption interceptor.
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.