欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密的示例代碼

 更新時(shí)間:2023年07月21日 11:31:51   作者:skywsp  
用戶(hù)的一些敏感數(shù)據(jù),例如手機(jī)號(hào)、郵箱、身份證等信息,在數(shù)據(jù)庫(kù)以明文存儲(chǔ)時(shí)會(huì)存在數(shù)據(jù)泄露的風(fēng)險(xiǎn),因此需要進(jìn)行加密,解密等功能,接下來(lái)本文就給大家介紹基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密,需要的朋友可以參考下

一、背景

用戶(hù)的一些敏感數(shù)據(jù),例如手機(jī)號(hào)、郵箱、身份證等信息,在數(shù)據(jù)庫(kù)以明文存儲(chǔ)時(shí)會(huì)存在數(shù)據(jù)泄露的風(fēng)險(xiǎn),因此需要進(jìn)行加密, 但存儲(chǔ)數(shù)據(jù)再被取出時(shí),需要進(jìn)行解密,因此加密算法需要使用對(duì)稱(chēng)加密算法。

常用的對(duì)稱(chēng)加密算法有AES、DES、RC、BASE64等等,各算法的區(qū)別與優(yōu)劣請(qǐng)自行百度。

本案例采用AES算法對(duì)數(shù)據(jù)進(jìn)行加密。

二、MybatisPlus攔截器介紹

本文基于SpringBoot+MybatisPlus(3.5.X)+MySQL8架構(gòu),Dao層與DB中間使用MP的攔截器機(jī)制,對(duì)數(shù)據(jù)存取過(guò)程進(jìn)行攔截,實(shí)現(xiàn)數(shù)據(jù)的加解密操作。

三、使用方法

該加解密攔截器功能在wutong-base-dao包(公司內(nèi)部包)已經(jīng)實(shí)現(xiàn),如果您的項(xiàng)目已經(jīng)依賴(lài)了base-dao,就可以直接使用。

另外,在碼云上有Demo案例,見(jiàn): java-test: java練習(xí)Demo項(xiàng)目 - Gitee.com

基于wutong-base-dao包的使用步驟如下。

1、添加wutong-base-dao依賴(lài)

<dependency>
    <groupId>com.talkweb</groupId>
    <artifactId>wutong-base-dao</artifactId>
    <version>請(qǐng)使用最新版本</version>
</dependency>

2、在yaml配置開(kāi)關(guān),啟用加解密

mybatis-plus:
  wutong:
    encrypt:
      # 是否開(kāi)啟敏感數(shù)據(jù)加解密,默認(rèn)false
      enable: true
      # AES加密秘鑰,可以使用hutool的SecureUtil工具類(lèi)生成
      secretKey: yourSecretKey

3、定義PO類(lèi)

實(shí)體類(lèi)上使用自定義注解,來(lái)標(biāo)記需要進(jìn)行加解密

// 必須使用@EncryptedTable注解
@EncryptedTable
@TableName(value = "wsp_user")
public class UserEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String name;
    // 使用@EncryptedColumn注解
    @EncryptedColumn
    private String mobile;
    // 使用@EncryptedColumn注解
    @EncryptedColumn
    private String email;
}

4、定義API接口

通過(guò)MP自帶API、Lambda、自定義mapper接口三種方式進(jìn)行測(cè)試

/**
 * 用戶(hù)表控制器
 *
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-01-11
 */
@RestController
@RequestMapping("/user")
public class UserController {
    @Resource(name = "userServiceImpl")
    private IUserService userService;
    @Resource(name = "userXmlServiceImpl")
    private IUserService userXmlService;
    /**
     * 測(cè)試解密
     */
    @GetMapping(name = "測(cè)試解密", value = "/detail")
    public UserEntity detail(Long id) {
        // 測(cè)試MP API
//        UserEntity entity = userService.getById(id);
        // 測(cè)試自定義Mapper接口
        UserEntity entity = userXmlService.getById(id);
        if (null == entity) {
            return new UserEntity();
        }
        return entity;
    }
    /**
     * 新增用戶(hù)表,測(cè)試加密
     */
    @GetMapping(name = "新增用戶(hù)表,測(cè)試加密", value = "/add")
    public UserEntity add(UserEntity entity) {
        // 測(cè)試MP API
//        userService.save(entity);
        // 測(cè)試自定義Mapper接口
        userXmlService.save(entity);
        return entity;
    }
    /**
     * 修改用戶(hù)表
     */
    @GetMapping(name = "修改用戶(hù)表", value = "/update")
    public UserEntity update(UserEntity entity) {
        // 測(cè)試MP API
//        userService.updateById(entity);
        // 測(cè)試Lambda
//        LambdaUpdateWrapper<UserEntity> wrapper = new LambdaUpdateWrapper<>();
//        wrapper.eq(UserEntity::getId, entity.getId());
//        wrapper.set(UserEntity::getMobile, entity.getMobile());
//        wrapper.set(UserEntity::getName, entity.getName());
//        wrapper.set(UserEntity::getEmail, entity.getEmail());
//        userService.update(wrapper);
        // 測(cè)試自定義Mapper接口
        userXmlService.updateById(entity);
        return entity;
    }
}

四、實(shí)現(xiàn)原理

1、自定義注解

根據(jù)注解進(jìn)行數(shù)據(jù)攔截

/**
 * 需要加解密的實(shí)體類(lèi)用這個(gè)注解
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-05-31
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface EncryptedTable {
}
/**
 * 需要加解密的字段用這個(gè)注解
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-05-31
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptedColumn {
}

2、定義攔截器

加密攔截器EncryptInterceptor

 
/**
 * 加密攔截器
 *
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-05-31
 */
@SuppressWarnings({"rawtypes"})
public class EncryptInterceptor extends JsqlParserSupport implements InnerInterceptor {
    /**
     * 變量占位符正則
     */
    private static final Pattern PARAM_PAIRS_RE = Pattern.compile("#\\{ew\\.paramNameValuePairs\\.(" + Constants.WRAPPER_PARAM + "\\d+)\\}");
    @Override
    public void beforeUpdate(Executor executor, MappedStatement mappedStatement, Object parameterObject) throws SQLException {
        if (Objects.isNull(parameterObject)) {
            return;
        }
        // 通過(guò)MybatisPlus自帶API(save、insert等)新增數(shù)據(jù)庫(kù)時(shí)
        if (!(parameterObject instanceof Map)) {
            if (needToDecrypt(parameterObject.getClass())) {
                encryptEntity(parameterObject);
            }
            return;
        }
        Map paramMap = (Map) parameterObject;
        Object param;
        // 通過(guò)MybatisPlus自帶API(update、updateById等)修改數(shù)據(jù)庫(kù)時(shí)
        if (paramMap.containsKey(Constants.ENTITY) && null != (param = paramMap.get(Constants.ENTITY))) {
            if (needToDecrypt(param.getClass())) {
                encryptEntity(param);
            }
            return;
        }
        // 通過(guò)在mapper.xml中自定義API修改數(shù)據(jù)庫(kù)時(shí)
        if (paramMap.containsKey("entity") && null != (param = paramMap.get("entity"))) {
            if (needToDecrypt(param.getClass())) {
                encryptEntity(param);
            }
            return;
        }
        // 通過(guò)UpdateWrapper、LambdaUpdateWrapper修改數(shù)據(jù)庫(kù)時(shí)
        if (paramMap.containsKey(Constants.WRAPPER) && null != (param = paramMap.get(Constants.WRAPPER))) {
            if (param instanceof Update && param instanceof AbstractWrapper) {
                Class<?> entityClass = mappedStatement.getParameterMap().getType();
                if (needToDecrypt(entityClass)) {
                    encryptWrapper(entityClass, param);
                }
            }
            return;
        }
    }
    /**
     * 校驗(yàn)該實(shí)例的類(lèi)是否被@EncryptedTable所注解
     */
    private boolean needToDecrypt(Class<?> objectClass) {
        EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);
        return Objects.nonNull(sensitiveData);
    }
    /**
     * 通過(guò)API(save、updateById等)修改數(shù)據(jù)庫(kù)時(shí)
     *
     * @param parameter
     */
    private void encryptEntity(Object parameter) {
        //取出parameterType的類(lèi)
        Class<?> resultClass = parameter.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = null;
                try {
                    object = field.get(parameter);
                } catch (IllegalAccessException e) {
                    continue;
                }
                //只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;
                    //對(duì)注解的字段進(jìn)行逐一加密
                    try {
                        field.set(parameter, AESUtils.encrypt(value));
                    } catch (IllegalAccessException e) {
                        continue;
                    }
                }
            }
        }
    }
    /**
     * 通過(guò)UpdateWrapper、LambdaUpdateWrapper修改數(shù)據(jù)庫(kù)時(shí)
     *
     * @param entityClass
     * @param ewParam
     */
    private void encryptWrapper(Class<?> entityClass, Object ewParam) {
        AbstractWrapper updateWrapper = (AbstractWrapper) ewParam;
        String sqlSet = updateWrapper.getSqlSet();
        String[] elArr = sqlSet.split(",");
        Map<String, String> propMap = new HashMap<>(elArr.length);
        Arrays.stream(elArr).forEach(el -> {
            String[] elPart = el.split("=");
            propMap.put(elPart[0], elPart[1]);
        });
        //取出parameterType的類(lèi)
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (Objects.isNull(sensitiveField)) {
                continue;
            }
            String el = propMap.get(field.getName());
            Matcher matcher = PARAM_PAIRS_RE.matcher(el);
            if (matcher.matches()) {
                String valueKey = matcher.group(1);
                Object value = updateWrapper.getParamNameValuePairs().get(valueKey);
                updateWrapper.getParamNameValuePairs().put(valueKey, AESUtils.encrypt(value.toString()));
            }
        }
    }
}

解密攔截器

/**
 * 解密攔截器
 *
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-05-31
 */
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Component
public class DecryptInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        if (resultObject instanceof ArrayList) {
            //基于selectList
            ArrayList resultList = (ArrayList) resultObject;
            if (!resultList.isEmpty() && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    //逐一解密
                    decrypt(result);
                }
            }
        } else if (needToDecrypt(resultObject)) {
            //基于selectOne
            decrypt(resultObject);
        }
        return resultObject;
    }
    /**
     * 校驗(yàn)該實(shí)例的類(lèi)是否被@EncryptedTable所注解
     */
    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        EncryptedTable sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptedTable.class);
        return Objects.nonNull(sensitiveData);
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
    private <T> T decrypt(T result) throws Exception {
        //取出resultType的類(lèi)
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptedColumn注解的字段
            EncryptedColumn sensitiveField = field.getAnnotation(EncryptedColumn.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);
                //只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;
                    //對(duì)注解的字段進(jìn)行逐一解密
                    field.set(result, AESUtils.decrypt(value));
                }
            }
        }
        return result;
    }
}

五、其他實(shí)現(xiàn)方案

在技術(shù)調(diào)研過(guò)程中,還測(cè)試了另外兩種便宜實(shí)現(xiàn)方案,由于無(wú)法覆蓋MP自帶API、Lambda、自定義API等多種場(chǎng)景,因此未采用。

1、使用字段類(lèi)型處理器

字段類(lèi)型處理器的[官方文檔點(diǎn)這里],不能處理LambdaUpdateWrapper更新數(shù)據(jù)時(shí)加密的場(chǎng)景。

自定義類(lèi)型處理器,實(shí)現(xiàn)加解密:

 
/**
 * @author wangshaopeng@talkweb.com.cn
 * @desccription 加密類(lèi)型字段處理器
 * @date 2023/5/31
 */
public class EncryptTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AESUtils.encrypt(parameter));
    }
    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        final String value = rs.getString(columnName);
        return AESUtils.decrypt(value);
    }
    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        final String value = rs.getString(columnIndex);
        return AESUtils.decrypt(value);
    }
    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        final String value = cs.getString(columnIndex);
        return AESUtils.decrypt(value);
    }
}

在實(shí)體屬性上進(jìn)行指定

// @TableName注解必須指定autoResultMap = true
@EncryptedTable
@TableName(value = "wsp_user", autoResultMap = true)
public class UserEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String mobile;
    @TableField(typeHandler = EncryptTypeHandler.class)
    private String email;
}

2、自動(dòng)填充功能

自動(dòng)填充功能的[官方文檔點(diǎn)這里],不能處理LambdaUpdateWrapper、自定義mapper接口更新數(shù)據(jù)時(shí)加密的場(chǎng)景,不支持解密的需求。

自定義類(lèi)型處理器,實(shí)現(xiàn)加解密:

 
/**
 * Mybatis元數(shù)據(jù)填充處理類(lèi),僅能處理MP的函數(shù),不能處理mapper.xml中自定義的insert、update
 *
 * @author wangshaopeng@talkweb.com.cn
 * @Date 2023-01-11
 */
public class DBMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        String mobile = (String) metaObject.getValue("mobile");
        this.strictInsertFill(metaObject, "mobile", String.class, AESUtils.encrypt(mobile));
        String email = (String) metaObject.getValue("email");
        this.strictInsertFill(metaObject, "email", String.class, AESUtils.encrypt(email));
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        String mobile = (String) metaObject.getValue("mobile");
        this.strictUpdateFill(metaObject, "mobile", String.class, AESUtils.encrypt(mobile));
        String email = (String) metaObject.getValue("email");
        this.strictUpdateFill(metaObject, "email", String.class, AESUtils.encrypt(email));
    }
}
 

在實(shí)體類(lèi)上指定自動(dòng)填充策略

@EncryptedTable
@TableName(value = "wsp_user")
public class UserEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String mobile;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String email;
}

以上就是基于Mybatis-Plus攔截器實(shí)現(xiàn)MySQL數(shù)據(jù)加解密的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于Mybatis-Plus實(shí)現(xiàn)MySQL數(shù)據(jù)加解密的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論