preface

  • Mybatis interceptor can be used to query and decrypt MySQL data
  • A limitation question was raised:
    • The main implementation logic is in the MybatisCryptHandler processing tool class, the current way can only handle request parameters and query results are object class rather than string type, in the next article will describe how to filter for string interception.
  • So this article is how to solve the string filtering interception. Mybatis interceptor can also intercept string parameters.
    • Not all string data needs to be encrypted and decrypted. The data is encrypted and decrypted only when the mobile phone number is used as the input parameter and the query result. Mybatis interceptor cannot directly distinguish whether the intercepted string needs encryption and decryption, so it needs to configure additional string interception and filtering (the string here includes: mobile phone number, real name, ID card number, bank card number, Alipay account and other data).

Spring section interception

  • Spring aop is widely used in project scenarios, and this time it is used for string interception and filtering.
  • Add the SpringBoot aspect Maven dependency
<dependency> <groupId>org.aspectj</groupId> <artifactId> aspectJrt </artifactId> <version>1.9.2</version> </dependency> < the dependency > < groupId > org. Aspectj < / groupId > < artifactId > aspectjweaver < / artifactId > < version > 1.9.2 < / version > </dependency>Copy the code
  • [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] [ProceedingJoinPoint] There are two more blocking methods, proceed, to get the result of the proxy method.
/** * <p> Applies to class: indicates that the current entity needs to decrypt the result. * <p> Applies to field: indicates that the current entity needs to decrypt the result. * <p> Applies to method: indicates that the current mapper method will be intercepted by the section and data will be decrypted and decrypted. <p> <p> <p> <p> <p> < P > < P > < P > The {@link #encryptParamIndex} property must have a value if applied to a method and an input parameter needs to be encrypted. Preoperative * * @author * @date 2022/ Ap */ @documented @Inherited @target ({ElementType.FIELD, ElementType.METHOD, Elementtype.type}) @Retention(retentionPolicy.runtime) public @interface Crypt {/** * decrypt */ Boolean decrypt () default true; /** * Default fields need to be encrypted */ Boolean encrypt () default true; */ Boolean subObject () default false; /** * encryptParamIndex () default {}; /** * encryptParamIndex () default {}; } /** * @author ZRH * @date 2022/1/1 */ @slf4j@aspect @component public class MybatisCryptAspect { @Around(value = "@annotation(crypt)") public Object crypt (ProceedingJoinPoint joinPoint, Crypt crypt) throws Throwable { Object result = null; try { Object[] params = joinPoint.getArgs(); If (crypt.encrypt() && crypt.encryptParamIndex().length > 0) {for (int I: Crypt.encryptparamindex ()) {params[I] = paramEncrypt(params[I]); } } result = joinPoint.proceed(params); // If (crypt.decrypt()) {if (result instanceof String) {result = resultDecrypt(result); } else if (result instanceof List) { result = ((List<? >) result).stream().map(this::resultDecrypt).collect(Collectors.toList()); }}} catch (Exception e) {log.error("MybatisCryptAspect encryption method Exception ", e); } return result; } / decryption query results * * * * @ param result * @ return * / private Object resultDecrypt (Object result) {if (null = = result | |! (result instanceof String)) { return result; } return AesTools.decryptECB(String.valueOf(result)); } /** * Query parameter encryption * @param result * @return */ private Object paramEncrypt (Object result) {if (null == result) {return null; } if (result instanceof String) { return AesTools.encryptECB(String.valueOf(result)); } if (result instanceof List) { return ((List<? >) result).stream().map(o -> AesTools.encryptECB(String.valueOf(o))).collect(Collectors.toList()); } return result; }}Copy the code
  • In real projects, there is very little business code that directly uses strings as input arguments and query results, and even less business code that only locates specific fields that need to be encrypted and decrypted. Here you can use annotations on the corresponding Mapper method to use aop aspect form for encryption and decryption operations.
  • Because Mapper methods will eventually go through myBatis interceptor, object types will be intercepted by MyBatis interceptor encryption and decryption. So when you add the @crypt annotation to a method on the Mapper interface, the following scenarios occur:
    • The string argument queries the object type data, just the string into the encryption;
    • Object type queries string data, only decrypts string results;
    • String parameter query string data, string into the encryption + string result decryption;
    • If the method is multi-parameter, only one or more of the input parameter parameters need to be encrypted query, this parameter may be the first or the last parameter;
/** * @Author: ZRH * @Date: 2021/11/25 13:48 */ @mapper public interface PhoneDataMapper {/** * Select(" Select id, phone, user_phone userPhone, name, real_name realName from phone_data") List<PhoneData> selectAll (); /** * string parameter Query object type data * @param phone * @return */ @crypt (decrypt = false, encryptParamIndex = 0) @select (" Select ID, phone, user_phone userPhone, name, real_name realName from phone_data where user_phone = #{phone}") PhoneData selectByPhone (@Param("phone") String phone);  /** * Object type Query string data * @param phoneData * @return */ @crypt (encrypt = false) @select (" Select user_phone from phone_data where user_phone = #{userPhone}") String selectPhoneBy (PhoneData phoneData); /** * String parameter Query string data * @param phone * @return */ @crypt (encryptParamIndex = 1) @select (" Select user_phone from phone_data where phone = #{phone} and user_phone = #{userPhone}") String selectPhoneByPhone (@Param("phone") String phone, @Param("userPhone") String userPhone); @return */ @select (" Select id, phone, user_phone userPhone, name real_name realName from phone_data where user_phone = #{userPhone}") List<PhoneData> selectList (PhoneData phoneData); } /** * @Author: ZRH * @Date: 2022/1/5 11:55 */ @Slf4j @RestController public class AopMapperController { @Autowired private PhoneDataMapper phoneDataMapper; @getMapping ("/aop/select") public String select (@requestParam String phone) { PhoneData build = PhoneData.build(phone); / / object types into the join query returns data object type List < PhoneData > selectList = phoneDataMapper. SelectList (build); log.info(" selectList = {}", JSON.toJSONString(selectList)); / / no arguments returned by the query object data type List < PhoneData > List = phoneDataMapper. SelectAll (); log.info(" selectAll = " + JSON.toJSONString(list)); / / the string into the join query returns data object type PhoneData PhoneData = phoneDataMapper. SelectByPhone (phone); log.info(" selectByPhone = " + JSON.toJSONString(phoneData)); / / object type into the query returns a String data refs String phone1. = phoneDataMapper selectPhoneBy (build); log.info(" selectPhoneBy = " + phone1); / / the String into the data returned by the query String String refs selectPhoneByPhone = phoneDataMapper. SelectPhoneByPhone (phone); log.info(" selectPhoneByPhone = " + selectPhoneByPhone); return "ok"; }}Copy the code
  • When the project is started and the query interface is accessed, the SQL log output is as follows:
The 2022-01-07 16:15:21. 6300-896 the DEBUG [task XNIO - 1-1] C.M.W.M apper. PhoneDataMapper. SelectList: = = > Preparing: select id, phone, user_phone userPhone, name, real_name realName from phone_data where user_phone = ? The 2022-01-07 16:15:21. 6300-908 the DEBUG [task XNIO - 1-1] C.M.W.M apper. PhoneDataMapper. SelectList: = = > the Parameters: ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:21.956 DEBUG 6300 - [xnio-1 task-1] ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:21.956 DEBUG 6300 - [xnio-1 task-1] c.m.w.mapper.PhoneDataMapper.selectList : <== Total: 1 the 16:15:22 2022-01-07. 6300-007 the INFO] [task XNIO - 1-1 C.M.W eb. Controller. AopMapperController: selectList = [{"id":1,"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"}] The 2022-01-07 16:15:22. 6300-007 the DEBUG [task XNIO - 1-1] C.M.W.M apper. PhoneDataMapper. SelectAll: = = > Preparing: select id, phone, user_phone userPhone, name, Real_name realName from phone_data 2022-01-07 16:15:22.007 DEBUG 6300 - [xnio-1 task-1] c.m.w.mapper.PhoneDataMapper.selectAll : ==> Parameters: The 2022-01-07 16:15:22. 6300-044 the DEBUG [task XNIO - 1-1] C.M.W.M apper. PhoneDataMapper. SelectAll: < = = Total: 1 the 16:15:22 2022-01-07. 6300-045 the INFO] [task XNIO - 1-1 C.M.W eb. Controller. AopMapperController: selectAll = [{"id":1,"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"}] The 2022-01-07 16:15:22. 6300-051 the DEBUG [task XNIO - 1-1] C.M.W.M.P honeDataMapper. SelectByPhone: = = > Preparing: select id, phone, user_phone userPhone, name, real_name realName from phone_data where user_phone = ? The 2022-01-07 16:15:22. 6300-051 the DEBUG [task XNIO - 1-1] C.M.W.M.P honeDataMapper. SelectByPhone: = = > the Parameters: ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:22.087 DEBUG 6300 - [xnio-1 task-1] ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:22.087 DEBUG 6300 - [xnio-1 task-1] c.m.w.m.PhoneDataMapper.selectByPhone : <== Total: 1 the 16:15:22 2022-01-07. 6300-088 the INFO] [task XNIO - 1-1 C.M.W eb. Controller. AopMapperController: selectByPhone = {"id":1,"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"} The 2022-01-07 16:15:22. 6300-089 the DEBUG [task XNIO - 1-1] C.M.W.M.P honeDataMapper. SelectPhoneBy: = = > Preparing: select user_phone from phone_data where user_phone = ? The 2022-01-07 16:15:22. 6300-089 the DEBUG [task XNIO - 1-1] C.M.W.M.P honeDataMapper. SelectPhoneBy: = = > the Parameters: ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:22.126 DEBUG 6300 - [xnio-1 task-1] ZHlSotVArLBAviP2KWi3Cg==(String) 2022-01-07 16:15:22.126 DEBUG 6300 - [xnio-1 task-1] c.m.w.m.PhoneDataMapper.selectPhoneBy : <== Total: 1 the 16:15:22 2022-01-07. 6300-127 the INFO] [task XNIO - 1-1 C.M.W eb. Controller. AopMapperController: SelectPhoneBy 16:15:22 = 15222222222, 2022-01-07. 6300-127 the DEBUG task [XNIO - 1-1] C.M.W.M.P.S electPhoneByPhone: ==> Preparing: select user_phone from phone_data where phone = ? and user_phone = ? The 2022-01-07 16:15:22. 6300-127 the DEBUG task [XNIO - 1-1] C.M.W.M.P.S electPhoneByPhone: = = > the Parameters: 15222222222(String), ZHlSotVArLBAviP2KWi3Cg = = (String) the 2022-01-07 16:15:22. 6300-163 the DEBUG task [XNIO - 1-1] C.M.W.M.P.S electPhoneByPhone: < = = Total: 1 2022-01-07 16:15:22. 6300-164 the INFO] [task XNIO - 1-1 C.M.W eb. Controller. AopMapperController: selectPhoneByPhone = 15222222222Copy the code

conclusion

  • To summarize the implementation logic above:
    • In the AOP aspect, the proxy method parameters are obtained and the method annotations are used to determine whether it needs to be encrypted.
    • Gets the result of the execution of the proxy method, and then configures whether the decryption operation is required based on the annotations on the method.
    • This completes automatic secure encryption and decryption by adding annotations to the method.
  • If there are problems in the code or ideas that can be optimized, feel free to point them out. Learn with an open mind and make progress together