最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Mybatis攔截器安全加解密MySQL數(shù)據實戰(zhàn)

 更新時間:2022年01月13日 15:38:05   作者:superjava_  
本文主要介紹了Mybatis攔截器安全加解密MySQL數(shù)據實戰(zhàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

需求背景

  • 公司為了通過一些金融安全指標(政策問題)和防止數(shù)據泄漏,需要對用戶敏感數(shù)據進行加密,所以在公司項目中所有存儲了用戶信息的數(shù)據庫都需要進行數(shù)據加密改造。包括Mysql、redis、mongodb、es、HBase等。
  • 因為在項目中是使用springboot+mybatis方式連接數(shù)據庫進行增刪改查,并且項目是中途改造數(shù)據。所以為了不影響正常業(yè)務,打算這次改動盡量不侵入到業(yè)務代碼,加上mybatis開放的各種攔截器接口,所以就以此進行改造數(shù)據。
  • 本篇文章講述如何在現(xiàn)有項目中盡量不侵入業(yè)務方式進行Mysql加密數(shù)據,最后為了不降低查詢性能使用了注解,所以最后還是部分侵入業(yè)務。

Mybatis攔截器

Mybatis只能攔截指定類里面的方法:Executor、ParameterHandler、StatementHandler、ResultSetHandler。

  • Executor:攔截執(zhí)行器方法;
  • ParameterHandler:攔截參數(shù)方法;
  • StatementHandler:攔截sql構建方法;
  • ResultSetHandler:攔截查詢結果方法;

Mybatis提供的攔截器接口Interceptor

public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
 
  default void setProperties(Properties properties) {
    // NOP
  }
}

- Object intercept():代理對象都會調用的方法,這里可以執(zhí)行自定義攔截處理;
- Object plugin():可以用于判斷攔截器執(zhí)行類型;
- void setProperties():指定配置文件的屬性;

自定義攔截器中除了要實現(xiàn)Interceptor接口,還需要添加@Intercepts注解指定攔截對象。@Intercepts注解需配合@Signature注解使用

@Intercepts注解可以指定多個@Signature,type指定攔截類,method指定攔截方法,args攔截方法里的參數(shù)類型。

/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  Signature[] value();
}
 
/**
 * @author Clinton Begin
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  Class<?> type();
  String method();
  Class<?>[] args();
}

案例實戰(zhàn)

依據上述的mybatis攔截器的使用,下面就把實戰(zhàn)案例代碼提供一下。

Mybatis自定義攔截器

  • 在業(yè)務代碼里用戶信息是以明文傳遞的,所以為了不改動業(yè)務代碼,那么需要攔截器在插入或查詢數(shù)據庫數(shù)據前先加密,查詢結果解密操作。
  • 首先搭建一個springboot的項目,這里指定兩個mybatis攔截器,一個攔截請求參數(shù),一個攔截響應數(shù)據,并把攔截器注入到spring容器內。
/**
 * 對mybatis入參進行攔截加密
 * @author zrh
 */
@Slf4j
@Component
@Intercepts(@Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}))
public class MybatisEncryptInterceptor implements Interceptor {
 
    @Resource
    private com.mysql.web.mybatis.Interceptor.MybatisCryptHandler handler;
 
    @Override
    public Object intercept (Invocation invocation) {
        return invocation;
    }
 
    @SneakyThrows
    @Override
    public Object plugin (Object target) {
        if (target instanceof ParameterHandler) {
            // 對請求參數(shù)進行加密操作
            handler.parameterEncrypt((ParameterHandler) target);
        }
        return target;
    }
 
    @Override
    public void setProperties (Properties properties) {
    }
}

注意:ResultSetHandler對象對增刪改方法沒有攔截,需要增加Executor對象;

/**
 * 對mybatis查詢結果進行攔截解密,并對請求參數(shù)進行攔截解密還原操作
 * @author zrh
 */
@Slf4j
@Component
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
public class MybatisDecryptInterceptor implements Interceptor {
 
    @Resource
    private MybatisCryptHandler handler;
 
    @Override
    public Object intercept (Invocation invocation) throws Exception {
        // 獲取執(zhí)行mysql執(zhí)行結果
        Object result = invocation.proceed();
        if (invocation.getTarget() instanceof Executor) {
            // 對增刪改操作方法的請求參數(shù)進行解密還原操作
            checkEncryptByUpdate(invocation.getArgs());
            return result;
        }
        // 對查詢方法的請求參數(shù)進行解密還原操作
        checkEncryptByQuery(invocation.getTarget());
        // 對查詢結果進行解密
        return handler.resultDecrypt(result);
    }
 
    @Override
    public Object plugin (Object target) {
        return Plugin.wrap(target, this);
    }
 
    @Override
    public void setProperties (Properties properties) {
    }
 
    /**
     * 對請求參數(shù)進行解密還原操作
     * @param target
     */
    private void checkEncryptByQuery (Object target) {
        try {
            final Class<?> targetClass = target.getClass();
            final Field parameterHandlerFiled = targetClass.getDeclaredField("parameterHandler");
            parameterHandlerFiled.setAccessible(true);
            final Object parameterHandler = parameterHandlerFiled.get(target);
            final Class<?> parameterHandlerClass = parameterHandler.getClass();
            final Field parameterObjectField = parameterHandlerClass.getDeclaredField("parameterObject");
            parameterObjectField.setAccessible(true);
            final Object parameterObject = parameterObjectField.get(parameterHandler);
            handler.decryptFieldHandler(parameterObject);
        } catch (Exception e) {
            log.error("對請求參數(shù)進行解密還原操作異常:", e);
        }
    }
 
    /**
     * 對請求參數(shù)進行解密還原操作
     * @param args
     */
    private void checkEncryptByUpdate (Object[] args) {
        try {
            Arrays.stream(args).forEach(handler::decryptFieldHandler);
        } catch (Exception e) {
            log.error("對請求參數(shù)進行解密還原操作異常:", e);
        }
    }
}

在上述攔截器中,除了對入參進行加密和查詢結果解密操作外,還多了一步對請求參數(shù)進行解密還原操作。

這是因為對請求參數(shù)進行加密操作時改動的是原對象,如果不還原解密數(shù)據,這個對象如果在后續(xù)還有其他操作,那就會使用密文,導致數(shù)據紊亂。

這里其實想過不改動原對象,而是把原請求對象克隆一份,在克隆對象上進行加密,然后在去查詢數(shù)據庫??上Э赡苁亲约簩ybatis不夠熟悉吧,試了很久也不能把mybatis內的原對象替換為克隆對象,所以才就想了這個還原解密參數(shù)的方式。

2.png

如果對請求參數(shù)對象和查詢結果對象里的所有字段都進行加解密,那上述配置就基本完成。但在本次安全加解密需求中只針對指定字段(如手機號和真實姓名),現(xiàn)在這種全量字段加解密就不行,而且性能也低,畢竟加解密是很耗費服務器CPU運算資源的。

所以需要增加注解,在指定對象的屬性字段才進行加解密。

/**
 * <p>作用于類:標識當前實體需要進行結果解密操作.
 * <p>作用于字段:標識當前實體的字段需要進行加解密操作.
 * <p>作用于方法:標識當前mapper方法會被切面進行攔截,并進行數(shù)據的加解密操作.
 * <p>注意:如果作用于字段,那當前類必須先標注該注解,因為會優(yōu)先判斷類是否需要加解密,然后在判斷字段是否需要加解密,否則只作用于字段不會起效
 *
 * @author zrh
 * @date 2022/1/4
 */
@Documented
@Inherited
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {
    /**
     * 默認字段需要解密
     */
    boolean decrypt () default true;
    /**
     * 默認字段需要加密
     */
    boolean encrypt () default true;
    /**
     * 字段為對象時有用,默認當前對象不需要進行加解密
     */
    boolean subObject () default false;
    /**
     * 需要進行加密的字段列下標
     */
    int[] encryptParamIndex () default {};
}

其注解使用方式如下:

1.png

AesTools是對數(shù)據進行AES對稱加解密工具類

/**
 * AES加密工具
 *
 * @author zrh
 * @date 2022/1/3
 */
@Slf4j
public final class AesTools {
    private AesTools () {
    }
 
    private static final String KEY_ALGORITHM = "AES";
    private static final String ENCODING = "UTF-8";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    private static Cipher ENCODING_CIPHER = null;
    private static Cipher DECRYPT_CIPHER = null;
    /**
     * 秘鑰
     */
    private static final String KEY = "cab041-3c46-fed5";
 
    static {
        try {
            // 初始化cipher
            ENCODING_CIPHER = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            DECRYPT_CIPHER = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            //轉化成JAVA的密鑰格式
            SecretKeySpec keySpec = new SecretKeySpec(KEY.getBytes("ASCII"), KEY_ALGORITHM);
            ENCODING_CIPHER.init(Cipher.ENCRYPT_MODE, keySpec);
            DECRYPT_CIPHER.init(Cipher.DECRYPT_MODE, keySpec);
        } catch (Exception e) {
            log.error("初始化mybatis -> AES加解密參數(shù)異常:", e);
        }
    }
 
    /**
     * AES加密
     * @param content 加密內容
     * @return
     */
    public static String encryptECB (String content) {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        String encryptStr = content;
        try {
            byte[] encrypted = ENCODING_CIPHER.doFinal(content.getBytes(ENCODING));
            encryptStr = Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            log.info("mybatis -> AES加密出錯:{}", content);
        }
        return encryptStr;
    }
 
    /**
     * AES解密
     * @param content 解密內容
     * @return
     */
    public static String decryptECB (String content) {
        if (StringUtils.isEmpty(content)) {
            return content;
        }
        String decryptStr = content;
        try {
            byte[] decrypt = DECRYPT_CIPHER.doFinal(Base64.getDecoder().decode(content));
            decryptStr = new String(decrypt, ENCODING);
        } catch (Exception e) {
            log.info("mybatis -> AES解密出錯:{}", content);
        }
        return decryptStr;
    }
}

MybatisCryptHandler是對請求入參對象和查詢結果對象進行加解密操作工具類。

代碼稍許復雜,但實現(xiàn)邏輯簡單,主要為了防止重復加密,內置緩存,對遞歸對象掃描檢索,反射+注解獲取需要加解密字段等。

/**
 * @author zrh
 * @date 2022/1/2
 */
@Slf4j
@Component
public class MybatisCryptHandler {
 
    private final static ThreadLocal<List> THREAD_LOCAL = ThreadLocal.withInitial(() -> new ArrayList());
    private static final List<Field> EMPTY_FIELD_ARRAY = new ArrayList();
    /**
     * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration.
     */
    private static final Map<Class<?>, List<Field>> declaredFieldsCache = new ConcurrentHashMap<>(256);
 
    /**
     * 參數(shù)對外加密方法
     * @param handler
     */
    public void parameterEncrypt (ParameterHandler handler) {
        Object parameterObject = handler.getParameterObject();
        if (null == parameterObject || parameterObject instanceof String) {
            return;
        }
        encryptFieldHandler(parameterObject);
        removeLocal();
    }
 
    /**
     * 參數(shù)加密規(guī)則方法
     * @param sourceObject
     */
    private void encryptFieldHandler (Object sourceObject) {
        if (null == sourceObject) {
            return;
        }
        if (sourceObject instanceof Map) {
            ((Map<?, Object>) sourceObject).values().forEach(this::encryptFieldHandler);
            return;
        }
        if (sourceObject instanceof List) {
            ((List<?>) sourceObject).stream().forEach(this::encryptFieldHandler);
            return;
        }
        Class<?> clazz = sourceObject.getClass();
        if (!clazz.isAnnotationPresent(Crypt.class)) {
            return;
        }
        if (checkLocal(sourceObject)) {
            return;
        }
        setLocal(sourceObject);
        try {
            Field[] declaredFields = clazz.getDeclaredFields();
            // 獲取滿足加密注解條件的字段
            final List<Field> collect = Arrays.stream(declaredFields).filter(this::checkEncrypt).collect(Collectors.toList());
            for (Field item : collect) {
                item.setAccessible(true);
                Object value = item.get(sourceObject);
                if (null != value && value instanceof String) {
                    item.set(sourceObject, AesTools.encryptECB((String) value));
                }
            }
        } catch (Exception e) {
        }
    }
 
    /**
     * 解析注解 - 加密密方法
     * @param field
     * @return
     */
    private boolean checkEncrypt (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.encrypt();
    }
 
    /**
     * 查詢結果對外解密方法
     * @param resultData
     */
    public Object resultDecrypt (Object resultData) {
        if (resultData instanceof List) {
            return ((List<?>) resultData).stream().map(this::resultObjHandler).collect(Collectors.toList());
        }
        return resultObjHandler(resultData);
    }
 
    /**
     * 查詢結果解密規(guī)則方法
     * @param result
     */
    private Object resultObjHandler (Object result) {
        if (null == result) {
            return null;
        }
        Class<?> clazz = result.getClass();
        //獲取所有要解密的字段
        Field[] declaredFields = getAllFieldsCache(clazz);
        Arrays.stream(declaredFields).forEach(item -> {
            try {
                item.setAccessible(true);
                Object value = item.get(result);
                if (null != value && value instanceof String) {
                    item.set(result, AesTools.decryptECB((String) value));
                }
            } catch (Exception e) {
                log.error("DecryptException -> checkDecrypt:", e);
            }
        });
 
        Arrays.stream(declaredFields).filter(item -> checkSubObject(item)).forEach(item -> {
            item.setAccessible(true);
            try {
                Object data = item.get(result);
                if (data instanceof List) {
                    ((List<?>) data).forEach(this::resultObjHandler);
                }
            } catch (IllegalAccessException e) {
                log.error("DecryptException -> checkSubObject:{}", e);
            }
        });
        return result;
    }
 
    /**
     * 解析注解 - 解密方法
     * @param field
     * @return
     */
    private static boolean checkDecrypt (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.decrypt();
    }
 
    /**
     * 解析注解 - 子對象
     * @param field
     * @return
     */
    private static boolean checkSubObject (Field field) {
        Crypt crypt = field.getAnnotation(Crypt.class);
        return null != crypt && crypt.subObject();
    }
 
    /**
     * 對請求參數(shù)進行解密還原,
     * @param requestObject
     */
    public void decryptFieldHandler (Object requestObject) {
        if (null == requestObject) {
            return;
        }
        if (requestObject instanceof Map) {
            ((Map<?, Object>) requestObject).values().forEach(this::decryptFieldHandler);
            return;
        }
        if (requestObject instanceof List) {
            ((List<?>) requestObject).stream().forEach(this::decryptFieldHandler);
            return;
        }
        Class<?> clazz = requestObject.getClass();
        if (!clazz.isAnnotationPresent(Crypt.class)) {
            return;
        }
        try {
            Field[] declaredFields = clazz.getDeclaredFields();
            // 獲取滿足加密注解條件的字段
            final List<Field> collect = Arrays.stream(declaredFields).filter(this::checkEncrypt).collect(Collectors.toList());
            for (Field item : collect) {
                item.setAccessible(true);
                Object value = item.get(requestObject);
                if (null != value && value instanceof String) {
                    item.set(requestObject, AesTools.decryptECB((String) value));
                }
            }
        } catch (Exception e) {
        }
    }
 
    /**
     * 統(tǒng)一管理內存
     * @param o
     * @return
     */
    private boolean checkLocal (Object o) {
        return THREAD_LOCAL.get().contains(o);
    }
    private void setLocal (Object o) {
        THREAD_LOCAL.get().add(o);
    }
    private void removeLocal () {
        THREAD_LOCAL.get().clear();
    }
 
    /**
     * 獲取本類及其父類的屬性的方法
     * @param clazz 當前類對象
     * @return 字段數(shù)組
     */
    private static Field[] getAllFields (Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }
 
    /**
     * 獲取本類及其父類的屬性的方法
     * @param clazz 當前類對象
     * @return 字段數(shù)組
     */
    private static Field[] getAllFieldsCache (Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            if (clazz.isAnnotationPresent(Crypt.class)) {
                fieldList.addAll(getDeclaredFields(clazz));
            }
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        return fieldList.toArray(fields);
    }
 
    private static List<Field> getDeclaredFields (Class<?> clazz) {
        List<Field> result = declaredFieldsCache.get(clazz);
        if (result == null) {
            try {
                // 獲取滿足注解解密條件的字段
                result = Arrays.stream(clazz.getDeclaredFields()).filter(MybatisCryptHandler::checkDecrypt).collect(Collectors.toList());
                // 放入本地緩存
                declaredFieldsCache.put(clazz, (result.isEmpty() ? EMPTY_FIELD_ARRAY : result));
            } catch (Exception e) {
                log.error("getDeclaredFields:", e);
            }
        }
        return result;
    }
}

數(shù)據表準備

用戶的敏感信息包括有手機號、真實姓名、身份證、銀行卡號、支付寶賬號等幾種。下面使用手機號和姓名字段進行加解密案例。

先準備一張Mysql數(shù)據表,表里有兩個手機號和兩個姓名字段,可以用于安全加解密對比。

CREATE TABLE `phone_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主鍵',
  `phone` varchar(122) DEFAULT NULL COMMENT '明文手機號',
  `user_phone` varchar(122) DEFAULT NULL COMMENT '密文手機號',
  `name` varchar(122) DEFAULT NULL COMMENT '明文姓名',
  `real_name` varchar(122) DEFAULT NULL COMMENT '密文姓名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='測試加解密數(shù)據表';

項目demo搭建

首先搭建一個springboot的項目,把一些基礎配置類創(chuàng)建:如controller、service、mapper、xml、entity,為了快速簡易的demo示例,這里去掉service層

/**
 * @Author: ZRH
 * @Date: 2022/1/5 13:47
 */
@Data
public class PhoneData {
 
    private Integer id;
    private String phone;
    private String userPhone;
    private String name;
    private String realName;
 
    public static PhoneData build (String phone) {
        return build(null, phone);
    }
 
    public static PhoneData build (Integer id, String phone) {
        final PhoneData phoneData = new PhoneData();
        phoneData.setId(id);
        phoneData.setPhone(phone);
        phoneData.setUserPhone(phone);
        phoneData.setName(phone);
        phoneData.setRealName(phone);
        return phoneData;
    }
}
 
/**
 * @Author: ZRH
 * @Date: 2022/1/5 11:55
 */
@Slf4j
@RestController
public class AopMapperController {
 
    @Autowired
    private PhoneDataMapper phoneDataMapper;
 
    /**
     * 添加示例接口
     * @param phone
     * @return
     */
    @PostMapping("/aop/insert")
    public String insert (@RequestParam String phone) {
        PhoneData build = PhoneData.build(phone);
        phoneDataMapper.insert(build);
        log.info(" 插入的原數(shù)據 = {}", JSON.toJSONString(build));
        return "ok";
    }
 
    /**
     * 更新示例接口
     * @param id
     * @param phone
     * @return
     */
    @PostMapping("/aop/update")
    public String update (@RequestParam Integer id, @RequestParam String phone) {
        PhoneData build = PhoneData.build(id, phone);
        phoneDataMapper.updateById(build);
        log.info(" 插入的原數(shù)據 = {}", JSON.toJSONString(build));
        return "ok";
    }
 
    /**
     * 查詢示例接口
     * @param phone
     * @return
     */
    @GetMapping("/aop/select")
    public String select (@RequestParam String phone) {
        final PhoneData build = PhoneData.build(phone);
        // 對象類型入參查詢對象數(shù)據
        List<PhoneData> selectList = phoneDataMapper.selectList(build);
        log.info(" selectList = {}", JSON.toJSONString(selectList));
        return "ok";
    }
}
 
/**
 * @Author: ZRH
 * @Date: 2021/11/25 13:48
 */
@Mapper
public interface PhoneDataMapper {
 
    /**
     * 新增數(shù)據
     * @param phoneData
     */
    @Insert("insert into phone_data (phone, user_phone, name, real_name) values (#{phone}, #{userPhone}, #{name}, #{realName})")
    void insert (PhoneData phoneData);
 
    /**
     * 更新數(shù)據
     * @param phoneData
     */
    @Update("update phone_data set phone = #{phone}, user_phone = #{userPhone}, name = #{name}, real_name = #{realName} where id = #{id}")
    void updateById (PhoneData phoneData);
 
    /**
     * 無參查詢對象類型數(shù)據
     * @return
     */
    @Select("select id, phone, user_phone userPhone, name, real_name realName from phone_data where user_phone = #{userPhone}")
    List<PhoneData> selectList (PhoneData phoneData);
}

項目啟動,訪問添加、更新、查詢接口,其sql日志打印出結果如下:

2022-01-07 14:46:35.348 DEBUG 6688 --- [  XNIO-1 task-1] c.m.web.mapper.PhoneDataMapper.insert    : ==>  Preparing: insert into phone_data (phone, user_phone, name, real_name) values (?, ?, ?, ?) 
2022-01-07 14:46:35.348 DEBUG 6688 --- [  XNIO-1 task-1] c.m.web.mapper.PhoneDataMapper.insert    : ==> Parameters: 15222222222(String), ZHlSotVArLBAviP2KWi3Cg==(String), 15222222222(String), ZHlSotVArLBAviP2KWi3Cg==(String)
2022-01-07 14:46:35.421 DEBUG 6688 --- [  XNIO-1 task-1] c.m.web.mapper.PhoneDataMapper.insert    : <==    Updates: 1
2022-01-07 14:46:35.422  INFO 6688 --- [  XNIO-1 task-1] c.m.web.controller.AopMapperController   :  插入的原數(shù)據 = {"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"}
2022-01-07 14:46:54.470 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.updateById  : ==>  Preparing: update phone_data set phone = ?, user_phone = ?, name = ?, real_name = ? where id = ? 
2022-01-07 14:46:54.470 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.updateById  : ==> Parameters: 15222222222(String), ZHlSotVArLBAviP2KWi3Cg==(String), 15222222222(String), ZHlSotVArLBAviP2KWi3Cg==(String), 1(Integer)
2022-01-07 14:46:54.540 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.updateById  : <==    Updates: 1
2022-01-07 14:46:54.540  INFO 6688 --- [  XNIO-1 task-1] c.m.web.controller.AopMapperController   :  插入的原數(shù)據 = {"id":1,"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"}
2022-01-07 14:46:55.754 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.selectList  : ==>  Preparing: select id, phone, user_phone userPhone, name, real_name realName from phone_data where user_phone = ? 
2022-01-07 14:46:55.754 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.selectList  : ==> Parameters: ZHlSotVArLBAviP2KWi3Cg==(String)
2022-01-07 14:46:55.790 DEBUG 6688 --- [  XNIO-1 task-1] c.m.w.mapper.PhoneDataMapper.selectList  : <==      Total: 1
2022-01-07 14:46:55.790  INFO 6688 --- [  XNIO-1 task-1] c.m.web.controller.AopMapperController   :  selectList = [{"id":1,"name":"15222222222","phone":"15222222222","realName":"15222222222","userPhone":"15222222222"}]

MySQL數(shù)據庫中的數(shù)據

4.png

總結

總結一下上述實現(xiàn)邏輯:

  • 在Mybatis自定義攔截器中,對各種對數(shù)據庫的查詢參數(shù)進行攔截,判斷當前對象內字段是否需要加密,如果有注解就進行加密操作,否則就不操作。再對數(shù)據庫查詢出的結果進行攔截,判斷查詢結果對象內字段是否需要解密,如果有注解就進行解密操作,否則就不操作。
  • 并且增加了一步對本次請求參數(shù)進行參數(shù)還原解密操作。
  • 這樣通過類和字段上增加注解,就完成自動安全加解密操作。

上述攔截器實現(xiàn)方式有一定局限性:

  • 整個代碼邏輯還可以在優(yōu)化,比如之前對請求參數(shù)還原解密方式的優(yōu)化。
  • 主要實現(xiàn)邏輯是在MybatisCryptHandler處理工具類中,當前方式現(xiàn)只能處理請求參數(shù)和查詢結果是對象類而不是字符串類型,在下篇文章中會介紹如何針對字符串進行過濾攔截。

到此這篇關于詳解Mybatis攔截器安全加解密MySQL數(shù)據實戰(zhàn)的文章就介紹到這了,更多相關Mybatis攔截器安全加解密MySQL內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • java幾種排序算法的實現(xiàn)及簡單分析

    java幾種排序算法的實現(xiàn)及簡單分析

    這篇文章主要介紹了java幾種排序算法的實現(xiàn)及簡單分析,實例分析了插入排序、希爾排序、選擇排序等常用排序算法,并分析了各個算法的優(yōu)劣,需要的朋友可以參考下
    2015-05-05
  • Spring Cloud 如何保證微服務內安全

    Spring Cloud 如何保證微服務內安全

    這篇文章主要介紹了Spring Cloud 如何保證微服務內安全的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java實現(xiàn)表單提交(支持多文件同時上傳)

    Java實現(xiàn)表單提交(支持多文件同時上傳)

    本文介紹了Java、Android實現(xiàn)表單提交(支持多文件同時上傳)的方法,具有一定的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • Java不帶break將導致case穿透問題

    Java不帶break將導致case穿透問題

    這篇文章主要介紹了Java不帶break將導致case穿透問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-02-02
  • SpringBoot?Test的webEnvironment源碼解讀

    SpringBoot?Test的webEnvironment源碼解讀

    這篇文章主要為大家介紹了SpringBoot?Test的webEnvironment源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-09-09
  • 關于log4j日志擴展---自定義PatternLayout

    關于log4j日志擴展---自定義PatternLayout

    這篇文章主要介紹了關于log4j日志擴展---自定義PatternLayout,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • 淺談Java中的interface應用與面向接口編程

    淺談Java中的interface應用與面向接口編程

    這篇文章主要介紹了淺談Java中的interface應用與面向接口編程,Java的關鍵字interface應用,一個接口,多個實現(xiàn)類,面向接口編程,把業(yè)務邏輯線提取出來作為接口,具體的業(yè)務實現(xiàn)通過該接口的實現(xiàn)類來完成,需要的朋友可以參考下
    2023-10-10
  • mybatis-plus之如何根據數(shù)據庫主鍵定義字段類型

    mybatis-plus之如何根據數(shù)據庫主鍵定義字段類型

    這篇文章主要介紹了mybatis-plus之如何根據數(shù)據庫主鍵定義字段類型問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • Spring中獲取Bean對象的三種注入方式與兩種注入方法詳解

    Spring中獲取Bean對象的三種注入方式與兩種注入方法詳解

    平常的Java開發(fā)中程序員在某個類中需要依賴其它類的方法,下面這篇文章主要給大家介紹了關于Spring中獲取Bean對象的三種注入方式與兩種注入方法的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-03-03
  • Redis集群原理詳細分析

    Redis集群原理詳細分析

    Redis集群實現(xiàn)了對Redis的水平擴容,即啟動N個redis節(jié)點,將整個數(shù)據庫分布存儲在這N個節(jié)點中,每個節(jié)點存儲總數(shù)據的1/N。Redis集群通過分區(qū)來提供一定程度的可用,即使集群中有一部分節(jié)點失效或者無法進行通訊,集群也可以繼續(xù)處理命令請求
    2022-12-12

最新評論

阜康市| 桓台县| 靖边县| 上高县| 将乐县| 西丰县| 开阳县| 社会| 金堂县| 贡山| 颍上县| 马尔康县| 滨海县| 隆回县| 霍林郭勒市| 汪清县| 平南县| 永寿县| 巴马| 锦州市| 惠东县| 安泽县| 乐清市| 昌邑市| 宁陕县| 衡阳县| 邹平县| 永兴县| 岳阳县| 女性| 伊金霍洛旗| 松阳县| 垣曲县| 临泉县| 竹山县| 岳西县| 阳高县| 项城市| 峨边| 遂溪县| 瑞安市|