In actual production project, often need to information such as id card, phone number, real name and other sensitive data is encrypted database storage, but in the business code to manually add decryption is not sensitive information, elegant, even there is wrong, leakage encryption, business people need to know the actual encryption rules, etc.
This article will introduce the detailed process of intercepting encryption before storing sensitive data using springBoot + Mybatis interceptor + custom annotations.
What is Mybatis Plugin
Plugin mybatis Plugin plugin mybatis plugin plugin
MyBatis allows you to intercept calls at certain points during the execution of mapped statements. By default, MyBatis allows you to intercept method calls using plug-ins:
Executor (Update, query, flushStatements, commit, rollback, getTransaction, close, IsClosed) // Intercepts ParameterHandler when ParameterHandler is obtained and set. ResultSetHandler (handleResultSets, HandleOutputParameters) // SQL statement block StatementHandler (prepare, parameterize, Batch, update, query)Copy the code
In short, that is, in the whole cycle of SQL execution, we can arbitrarily cut into a certain point of THE SQL parameters, SQL execution result set, SQL statement itself, etc. Based on this feature, we can use it to uniformly encrypt the data that we need to encrypt (this is how the paging plugin pageHelper implements database paging queries).
Implement annotation-based interceptor for encryption and decryption of sensitive information
2.1 Implementation Roadmap
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 input parameters
Decrypt the outgoing parameter using the ResultSetHandler interceptor.The fields that need to be encrypted and decrypted may need to be changed flexibly. At this time, we define an annotation to annotate the fields that need to be encrypted, so that we can cooperate with the interceptor to encrypt and decrypt the required data.
The Mybatis Interceptor interface has the following methods to implement.
Public interface Interceptor {// Object Intercept (Invocation) throws Throwable; // Mybatis plugin default Object plugin(Object target) {return plugin. wrap(target, this); Default void setProperties(Properties Properties) {}}Copy the code
2.2 Define sensitive information annotations to be encrypted and decrypted
Define annotations for sensitive information classes such as the entity class POJOPO
/ / @inherited @target ({elementtype.type}) @Retention(retentionPolicy.runtime) public @interface SensitiveData { }Copy the code
Defines annotations for sensitive fields in the sensitive information class
/ / @inherited @target ({elementtype.field}) @Retention(retentionPolicy.runtime) public @interface SensitiveField { }Copy the code
2.3 Define encryption interfaces and their implementation classes
Define the encryption interface to facilitate the expansion of the encryption method (such as AES encryption algorithm expansion support PBE algorithm, only need to specify the injection can be)
Public interface EncryptUtil {/** * Encrypts ** @param declaredFields paramsObject Specifies the declared field * @param paramsObject ParamsType instance in mapper * @return T * @throws IllegalAccessException Field inaccessible exception */ <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException; }Copy the code
This article does not provide the AES encryption implementation class of EncryptUtil. AESUtil is a self-encapsulating AES encryption tool. (Search the official account of Java bosom friend, reply “2021”, send you a Java interview questions treasure book)
@Component public class AESEncrypt implements EncryptUtil { @Autowired AESUtil aesUtil; /** * Encryption ** @param declaredFields paramsObject Specifies the declared field * @param paramsObject Specifies the paramsType instance in mapper * @return T * @throws */ @override public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException { for (Field field : DeclaredFields) {// Extract all fields annotated by EncryptDecryptField SensitiveField SensitiveField = field.getAnnotation(SensitiveField.class); if (! Objects.isNull(sensitiveField)) { field.setAccessible(true); Object object = field.get(paramsObject); If (object instanceof String) {String value = (String) object; Set (paramsObject, aesutil.encrypt (value)); // Encrypt. } } } return paramsObject; }}Copy the code
2.4 Implement the access encryption interceptor
Myabtis package of org. Apache. Ibatis. Plugin. The Interceptor Interceptor interface requires us to implement the following three methods
Public interface Interceptor {// Core Interceptor Object Intercept (Invocation) throws Throwable; // Default Object plugin(Object target) {return plugin. wrap(target, this); Default void setProperties(Properties Properties) {}}Copy the code
Therefore, referring to the example in the official documentation, we define a custom entry encryption interceptor.
The @intercepts annotation opens the interceptor, and the @signature annotation defines the actual type of the interceptor.
@ in the Signature
The type property specifies that the current interceptor uses StatementHandler, ResultSetHandler, ParameterHandler, A method attribute of Executor specifies the specific methods that use each of the four types (you can look inside the class to see their methods). The args attribute specifies the precompiled statements . Here we use the ParameterHandler setParamters () method, intercept mapper. XML paramsType instance (i.e. in each containing paramsType attribute mapper statement, perform the interceptors, Intercepting instances of paramsType)
/** * Encryption interceptors * Note that the @Component annotation must be preceded by ** @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) throws Throwable {// @signature specifies type= The invocation of parameterHandler. GetTarget () is parameterHandler. ParameterHandler = (ParameterHandler) Invocation. GetTarget (); // Get the parameter image, The mapper paramsType instances in the Field parameterField = parameterHandler. GetClass () getDeclaredField (" parameterObject "); parameterField.setAccessible(true); ParameterObject = parameterField.get(parameterHandler); if (parameterObject ! = null) { Class<? > parameterObjectClass = parameterObject.getClass(); / / check whether the instance of class annotation by @ SensitiveData SensitiveData SensitiveData = AnnotationUtils. FindAnnotation (parameterObjectClass, SensitiveData.class); If (objects.nonnull (sensitiveData)) { Incoming encryption methods Field [] declaredFields = parameterObjectClass. GetDeclaredFields (); encryptUtil.encrypt(declaredFields, parameterObject); } } return invocation.proceed(); } @override public Object plugin(Object o) {return plugin.wrap (o, this); @override public void setProperties(Properties Properties) {}Copy the code
Custom encryption interception encryption is complete.
2.5 Define decryption interfaces and their implementation classes
Decrypt interface, where result is an instance of resultType in mapper. XML.
Public interface DecryptUtil {/** * Decrypts ** @param result resultType instance * @return T * @throws IllegalAccessException Field access exception */ <T> T decrypt(T result) throws IllegalAccessException; }Copy the code
Decryption interface AES tool decrypts implementation classes
public class AESDecrypt implements DecryptUtil { @Autowired AESUtil aesUtil; /** * Decrypt ** @param result resultType instance * @return T * @throws IllegalAccessException Field unreachable exception */ @override public <T> T Throws IllegalAccessException {// Retrieve the resultType Class<? > resultClass = result.getClass(); Field[] declaredFields = resultClass.getDeclaredFields(); for (Field field : DeclaredFields) {// Extract all fields annotated by EncryptDecryptField SensitiveField SensitiveField = field.getAnnotation(SensitiveField.class); if (! Objects.isNull(sensitiveField)) { field.setAccessible(true); Object object = field.get(result); If (object instanceof String) {String value = (String) object; Field. Set (result, aesutil.decrypt (value)); // Decrypts the annotated fields one by one. } } } return result; }}Copy the code
2.6 Define the 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) throws Throwable {// fetch the query resultObject 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); 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) { } }Copy the code
This completes the configuration of the decryption interceptor.
Annotate the fields in the entity class that need to be decrypted
In mapper, specify paramType=User resultType=User to implement encryption and decryption operations based on the Mybatis interceptor.