Mybatis插件应用之数据脱敏
利用Mybatis插件实现数据脱敏
功能介绍
利用mybatis中的plugin(拦截器,底层基于jdk动态代理实现),并结合自定义注解,实现对某些重要字段的加密和解密。
代码说明
-
2个自定义注解
/** * 标识需要加解密的字段 **/ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.FIELD) public @interface Encrypt { } |
1 2 3 4 5 6 7 | /** * 标识需要加解密的类 */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.TYPE) public @interface SensitiveData { } |
-
基于mybatis的自定义插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | /** * 自定义mybatis拦截器 */ @Component @Intercepts ({ @Signature (type = Executor. class , method = "query" , args = {MappedStatement. class , Object. class , RowBounds. class , ResultHandler. class }), @Signature (type = Executor. class , method = "update" , args = {MappedStatement. class , Object. class }) }) public class EncryptInterceptor implements Interceptor { /** * 加密标识 */ private static final int ENCRYPT_DATA = 0 ; /** * 解密标识 */ private static final int DECRYPT_DATA = 1 ; @Override public Object intercept(Invocation invocation) throws Throwable { Object result; //根据拦截的方法判断对字段加密还是解密 if (Objects.equals(invocation.getMethod().getName(), "query" )) { //对查询结果解密 result = decryptData(invocation); } else { //对插入数据加密 result = encryptData(invocation); } return result; } /** * 对查询结果解密入口 * @param invocation * @return * @throws Exception */ private Object decryptData(Invocation invocation) throws Exception { System.out.println( "解密..." ); //取出查询的结果 Object resultObj = invocation.proceed(); if (Objects.isNull(resultObj)) { return null ; } //结果为数组 if (resultObj instanceof List) { List<?> data = (List<?>) resultObj; if (CollectionUtils.isEmpty(data) || !findSensitiveAnnotation(data.get( 0 ))) { return null ; } for (Object item : data) { handle(item, DECRYPT_DATA); } return resultObj; } //结果为单个对象 if (findSensitiveAnnotation(resultObj)) { handle(resultObj, DECRYPT_DATA); } return resultObj; } /** * 对插入数据加密入口 * @param invocation * @return * @throws Exception */ private Object encryptData(Invocation invocation) throws Exception { System.out.println( "加密..." ); Object param = invocation.getArgs()[ 1 ]; if (Objects.isNull(param) || !findSensitiveAnnotation(param)) { return null ; } System.out.println( "原插入对象:" + param); handle(param, ENCRYPT_DATA); System.out.println( "加密后对象:" + param); return invocation.proceed(); } /** * 判断类是否包含@SensitiveData注解 * @param obj * @return */ private boolean findSensitiveAnnotation(Object obj) { return Objects.nonNull(AnnotationUtils.findAnnotation(obj.getClass(), SensitiveData. class )); } /** * 对数据解密或解密 * @param data * @param flag * @param <T> * @throws Exception */ public <T> void handle(T data, int flag) throws Exception { //遍历字段 for (Field field : data.getClass().getDeclaredFields()) { //取出被Encrypt注解的字段 if (Objects.isNull(field.getAnnotation(Encrypt. class ))) { continue ; } field.setAccessible( true ); Object val = field.get(data); if (val instanceof String) { if (flag == DECRYPT_DATA) { field.set(data, EncryptUtil.decrypt((String) val)); } else if (flag == ENCRYPT_DATA) { field.set(data, EncryptUtil.encrypt((String) val)); } else { return ; } } } } @Override public Object plugin(Object target) { return Interceptor. super .plugin(target); } @Override public void setProperties(Properties properties) { Interceptor. super .setProperties(properties); } } |
-
基于Base64类的加解密工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /** * 加解密工具类 */ public class EncryptUtil { private static final String DEFAULT_V = "6859505890402435" ; private static final String KEY = "***" ; private static final String ALGORITHM = "AES" ; private static SecretKeySpec getKey() { byte [] arrBTmp = EncryptUtil.KEY.getBytes(); // 创建一个空的16位字节数组(默认值为0) byte [] arrB = new byte [ 16 ]; for ( int i = 0 ; i < arrBTmp.length && i < arrB.length; i++) { arrB[i] = arrBTmp[i]; } return new SecretKeySpec(arrB, ALGORITHM); } /** * 加密 * @param content * @return * @throws Exception */ public static String encrypt(String content) throws Exception { final Base64.Encoder encoder = Base64.getEncoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); byte [] encrypted = cipher.doFinal(content.getBytes()); return encoder.encodeToString(encrypted); } /** * 解密 * @param content * @return * @throws Exception */ public static String decrypt(String content) throws Exception { final Base64.Decoder decoder = Base64.getDecoder(); SecretKeySpec keySpec = getKey(); Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); IvParameterSpec iv = new IvParameterSpec(DEFAULT_V.getBytes()); cipher.init(Cipher.DECRYPT_MODE, keySpec, iv); byte [] base64 = decoder.decode(content); byte [] original = cipher.doFinal(base64); return new String(original); } } |
-
3层架构连接mybatis
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @RestController @RequestMapping (value = "/api/v1/dork-h/encrypt" ) public class EncryptController { @Resource EncryptService encryptService; @GetMapping (value = "/search" ) public Result testEncrypt() { List<User> user = encryptService.getUser(); return Result.success(user); } @PostMapping (value = "/insert" ) public Result insert( @RequestBody User user) { boolean result = encryptService.insert(user); return Result.success(result); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | @Service public class EncryptService { @Resource EncryptMapper encryptMapper; public List<User> getUser(){ List<User> user = encryptMapper.getUser(); return user; } public boolean insert(User user) { return encryptMapper.insert(user); } } |
1 2 3 4 5 6 7 8 9 | @Mapper @Component public interface EncryptMapper { List<User> getUser(); boolean insert(User user); } |
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace="cn.com.dork.study.encrypt.EncryptMapper"> <insert id="insert" parameterType="cn.com.dork.test.User"> insert into test(name,pwd) values (#{name},#{pwd}) </insert> <select id="getUser" resultType="cn.com.dork.test.User"> SELECT id, name, pwd FROM test </select> </mapper>
-
http测试
1 | GET http: //localhost:8880/api/v1/dork-h/encrypt/search |
1 2 3 4 5 6 7 | POST http: //localhost:8880/api/v1/dork-h/encrypt/insert Content-Type: application/json { "name" : "test1010" , "pwd" : "password1010" } |
搞定~~~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix