MyBatis-Plus 优雅实现数据加密存储

文章目录

  • 前言
  • 一、数据库字段加解密实现
    • 1. 定义加密类型枚举
    • 2. 定义AES密钥和偏移量
    • 3. 配置定义使用的加密类型
    • 4. 加密解密接口
    • 5. 解密解密异常类
    • 6. 加密解密实现类
      • 6.1 AES加密解密实现类
      • 6.2 Base64加密解密实现类
    • 7. 实现数据库的字段保存加密与查询解密处理类
    • 8. MybatisPlus配置类
  • 二、数据库字段加密解密的使用
    • 1. 创建实体类
    • 2. 数据保存示例
    • 3. 数据查询示例


前言

本文将基于Mybatis-Plus讲述如何在数据的源头存储层保障其安全。我们都知道一些核心私密字段,比如说密码,手机号等在数据库层存储就不能明文存储,必须加密存储保证即使数据库泄露了也不会轻易曝光数据。


本文实现效果参考 plasticene-boot-starter-parent,更多信息在下面链接。

Github地址:https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent

当然也可以参考 mybatis-mate 为 mp 企业级模块,旨在更敏捷优雅处理数据。

Gitee地址:https://gitee.com/baomidou/mybatis-mate-examples

一、数据库字段加解密实现

1. 定义加密类型枚举

默认提供基于base64和AES加密算法,当然也可以自定义加密算法。

public enum Algorithm {
    BASE64,
    AES
}

2. 定义AES密钥和偏移量

@Data
@ConfigurationProperties(prefix = "ptc.encrypt")
public class EncryptProperties {
    /**
     * 加密算法 {@link Algorithm}
     */
    private Algorithm algorithm = Algorithm.BASE64;

    /**
     * aes算法需要秘钥key
     */
    private String key = "8iUJAD805IHO2vog";

    /**
     * aes算法需要一个偏移量
	 * AES算法的偏移量长度必须为16字节(128位)
     */
    private String iv = "cUTd1U+yxk8Dl6Cg";

}

AES的密钥和偏移量的生成可访问:AES 密钥在线生成器

若使用RSA,密钥对的生成可访问:在线生成非对称加密公钥私钥对

3. 配置定义使用的加密类型

这里我们使用aes加密算法:

  • application.yml
ptc:
  encrypt:
    algorithm: aes

4. 加密解密接口

public interface EncryptService {

    /**
     * 加密算法
     * @param content
     * @return
     */
    String encrypt(String content);

    /**
     * 解密算法
     * @param content
     * @return
     */
    String decrypt(String content);

}

5. 解密解密异常类

  • BizException.java
/**
 * 业务异常类
 */
@Data
public class BizException extends RuntimeException {

    private Integer code;

    public BizException() {
        super();
    }

    public BizException(String message) {
        super(message);
    }

    public BizException(Integer code, String message) {
        super(message);
        this.code = code;
    }

}

6. 加密解密实现类

6.1 AES加密解密实现类

@Slf4j
public class AESEncryptService implements EncryptService {
    @Resource
    private EncryptProperties encryptProperties;

    @Override
    public String encrypt(String content) {
        try {
            SecretKeySpec secretKey = new SecretKeySpec(encryptProperties.getKey().getBytes(StandardCharsets.UTF_8), Constants.AES);
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, Constants.AES);
            IvParameterSpec iv = new IvParameterSpec(encryptProperties.getIv().getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance(Constants.AES_CBC_CIPHER);
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);
            byte[] valueByte = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
            return Base64.getEncoder().encodeToString(valueByte);
        } catch (Exception e) {
            log.error("加密失败:", e);
            throw new BizException("加密失败");
        }
    }

    @Override
    public String decrypt(String content) {
        try {
            byte[] originalData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));
            SecretKeySpec secretKey = new SecretKeySpec(encryptProperties.getKey().getBytes(StandardCharsets.UTF_8), Constants.AES);
            byte[] enCodeFormat = secretKey.getEncoded();
            SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, Constants.AES);
            IvParameterSpec iv = new IvParameterSpec(encryptProperties.getIv().getBytes(StandardCharsets.UTF_8));
            Cipher cipher = Cipher.getInstance(Constants.AES_CBC_CIPHER);
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);
            byte[] valueByte = cipher.doFinal(originalData);
            return new String(valueByte);
        } catch (Exception e) {
            log.error("解密失败:", e);
            throw new BizException("解密失败");
        }
    }
}

6.2 Base64加密解密实现类

public class Base64EncryptService implements EncryptService {
    @Override
    public String encrypt(String content) {
        try {
            return Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            throw new RuntimeException("encrypt fail!", e);
        }
    }

    @Override
    public String decrypt(String content) {
        try {
            byte[] asBytes = Base64.getDecoder().decode(content);
            return new String(asBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("decrypt fail!", e);
        }
    }
}

7. 实现数据库的字段保存加密与查询解密处理类

接下来就可以基于加密算法,扩展 MyBatis 的 BaseTypeHandler 对实体字段数据进行加密解密

public class EncryptTypeHandler<T> extends BaseTypeHandler<T> {

    @Resource
    private EncryptService encryptService;

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, encryptService.encrypt((String)parameter));
    }
    @Override
    public T getNullableResult(ResultSet rs, String columnName) throws SQLException {
        String columnValue = rs.getString(columnName);
        //有一些可能是空字符
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        String columnValue = rs.getString(columnIndex);
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }

    @Override
    public T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        String columnValue = cs.getString(columnIndex);
        return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);
    }
}

8. MybatisPlus配置类

把EncryptService实现类注入到容器中

@Configuration
@MapperScan("com.chh.mapper")
@EnableConfigurationProperties({EncryptProperties.class})
public class MybatisPlusConfig {

    @Resource
    private EncryptProperties encryptProperties;

    // 分页插件
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));
        return interceptor;
    }

    @Bean
    public EncryptTypeHandler encryptTypeHandler() {
        return new EncryptTypeHandler();
    }

    @Bean
    @ConditionalOnMissingBean(EncryptService.class)
    public EncryptService encryptService() {
        Algorithm algorithm = encryptProperties.getAlgorithm();
        EncryptService encryptService;
        switch (algorithm) {
            case BASE64:
                encryptService = new Base64EncryptService();
                break;
            case AES:
                encryptService = new AESEncryptService();
                break;
            default:
                encryptService = null;
        }
        return encryptService;
    }

}

二、数据库字段加密解密的使用

1. 创建实体类

  • 在实体类上加上 @TableName(value = "表名", autoResultMap = true)
  • 在需要加密的属性上加上 @TableField(value = "字段", typeHandler = EncryptTypeHandler.class)
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName(value = "app_account_login", autoResultMap = true)
public class AppAccountLogin implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    /**
     * 名称
     */
    @TableField("name")
    private String name;

    /**
     * 登录账号
     */
    @TableField("login_account")
    private String loginAccount;

    /**
     * 登录密码
     */
	@TableField(value = "login_password", typeHandler = EncryptTypeHandler.class)
    private String loginPassword;

    /**
     * 激活状态
     */
    @TableField("enabled")
    private Boolean enabled;

    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

}

2. 数据保存示例

AppAccountLogin appAccountLogin = new AppAccountLogin();
appAccountLogin.setName("test");
appAccountLogin.setLoginAccount("123456789");
appAccountLogin.setLoginPassword("abc123456");
appAccountLoginService.save(appAccountLogin);

保存结果:

请添加图片描述

3. 数据查询示例

System.out.println(appAccountLoginService.getById(4));

查询结果:

AppAccountLogin(id=4, name=test, loginAccount=123456789, loginPassword=abc123456, enabled=true, createTime=Fri Feb 02 10:02:41 CST 2024)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/402899.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Sora热潮下,如何充分利用AI减少人工测试需求?

近日&#xff0c;OpenAI发布视频生成模型Sora&#xff0c;再次引发全球科技圈讨论热潮。Sora可以根据用户输入的简短文本指令&#xff0c;生成长达1分钟的高清视频&#xff0c;视频画面具有真实感&#xff0c;带有些许电影质感。 根据IDC的预测&#xff0c;未来五年内&#xff…

Java项目:20 基于SSM实现的支教管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 ssm支教管理系统&#xff08;前台后台&#xff09; 前台角色&#xff1a;支教学校志愿者 支教学校功能模块&#xff1a;支教学校查询报名职位发布已…

Android横竖屏切换configChanges=“screenSize|orientation“避免activity销毁重建,Kotlin

Android横竖屏切换configChanges"screenSize|orientation"避免activity销毁重建&#xff0c;Kotlin 如果不在Androidmanifest.xml设置activity的&#xff1a; android:configChanges"screenSize|orientation" 那么&#xff0c;每次横竖屏切换activity都会…

【鸿蒙 HarmonyOS 4.0】数据持久化

一、数据持久化介绍 数据持久化是将内存数据(内存是临时的存储空间)&#xff0c;通过文件或数据库的形式保存在设备中。 HarmonyOS提供两种数据持久化方案&#xff1a; 1.1、用户首选项&#xff08;Preferences&#xff09;&#xff1a; 通常用于保存应用的配置信息。数据通…

如何使用Express框架构建一个简单的Web应用

在这个数字化时代&#xff0c;Web应用的需求越来越多样化和复杂化。在前端开发领域&#xff0c;Express框架作为一个快速、灵活的Node.js Web应用程序框架&#xff0c;拥有强大的功能和丰富的生态系统&#xff0c;深受开发者们的青睐。本篇博客将带您一步步探索如何使用Express…

JS进阶——解构赋值

数组解构 基本&#xff1a; let [a, b, c] [1, 2, 3]; // a 1 // b 2 // c 3 可嵌套 let [a, [[b], c]] [1, [[2], 3]]; // a 1 // b 2 // c 3 可忽略 let [a, , b] [1, 2, 3]; // a 1 // b 3 不完全解构 let [a 1, b] []; // a 1, b undefined 剩余运…

LLMChain使用 | RouterChain的使用 - 用本地大模型搭建多Agents

单个本地大模型搭建参考博客 单个Chain&#xff1a;面对一个需求&#xff0c;我们需要创建一个llmchain&#xff0c;设置一个prompt模板&#xff0c;这个chain能够接收一个用户input&#xff0c;并输出一个结果&#xff1b;多个Chain&#xff1a;考虑到同时面对多个需求&#x…

【计算机网络】网络基础知识

一. 网络发展史 独立模式&#xff08;单机模式&#xff09;&#xff1a;计算机之间相互独立&#xff0c;各自拥有独立的数据。 网络互连&#xff1a;将多台计算机连接在一起&#xff0c;完成数据共享。 随着时代的发展&#xff0c;越来越需要计算机之间进行互相通信&#…

#1.4w字长文#仿抖音项目架构设计与实现

一、项目介绍 本文介绍了一个Web端短视频应用&#xff0c;致力于为用户提供交互友好、功能完备的短视频浏览体验和直播体验。 集成了Gorse推荐算法&#xff0c;旨在为用户提供更个性化的推荐视频流和更权威的热门视频流。接入大模型&#xff0c;通过对视频内容进行语言分析&a…

旧物回收小程序开发,开启绿色生活新篇章

随着科技的发展和人们生活水平的提高&#xff0c;物质生活的丰富带来了大量的废弃物。如何合理处理这些废弃物&#xff0c;实现资源的再利用&#xff0c;已成为社会关注的焦点。旧物回收小程序的开发与应用&#xff0c;为这一问题提供了有效的解决方案。本文将探讨旧物回收小程…

洛谷C++简单题小练习day14—闰年推算小程序

day14--闰年推算小程序--2.18 习题概述 题目描述 输入 x,y&#xff0c;输出 [x,y] 区间中闰年个数&#xff0c;并在下一行输出所有闰年年份数字&#xff0c;使用空格隔开。 输入格式 输入两个正整数 x,y&#xff0c;以空格隔开。 输出格式 第一行输出一个正整数&#xf…

C++笔记:OOP三大特性之多态

前言 本博客中的代码和解释都是在VS2019下的x86程序中进行的&#xff0c;涉及的指针都是 4 字节&#xff0c;如果要其他平台下测试&#xff0c;部分代码需要改动。比如&#xff1a;如果是x64程序&#xff0c;则需要考虑指针是8bytes问题等等。 文章目录 前言一、多态的概念二、…

Linux网络编程(三-UDP协议)

目录 一、UDP概述 二、UDP的首部格式 三、UDP缓冲区 四、基于UDP的应用层协议 五、常见问题 一、UDP概述 UDP(User Datagram Protocol&#xff0c;用户数据协议报)是传输层协议&#xff0c;提供不可靠服务&#xff0c;其特点包括&#xff1a; 无连接&#xff1a;知道对端…

探针类型、方式及实验

目录 1、tcpSocket方式 2、就绪检测 3、就绪检测2 4、启动、退出动作 5、探针 5.1探针的三种类型 5.2探针的三种方式 1、tcpSocket方式 vim tcpsocket.yaml apiVersion: v1 kind: Pod metadata:name: probe-tcp spec:containers:- name: nginximage: soscscs/myapp:v1live…

300分钟吃透分布式缓存-10讲:MC是怎么定位key的?

我们在进行 Mc 架构剖析时&#xff0c;除了学习 Mc 的系统架构、网络模型、状态机外&#xff0c;还对 Mc 的 slab 分配、Hashtable、LRU 有了简单的了解。本节课&#xff0c;将进一步深入学习这些知识点。 接下来&#xff0c;进入 Memcached 进阶的学习。会讲解 Mc 是如何进行…

UIKit 在 UICollectionView 中拖放交换 Cell 视图的极简实现

概览 UIKit 中的 UICollectionView 视图是我们显示多列集合数据的不二选择&#xff0c;而丰富多彩的交互操作更是我们选择 UICollectionView 视图的另一个重要原因。 如上图所示&#xff1a;我们实现了在 UICollectionView 中拖放交换任意两个 Cell 子视图的功能&#xff0c;这…

YOLOv9来了! 使用可编程梯度信息学习你想学的内容, v7作者新作!【文献速读】

YOLOv9文献速读&#xff0c;本文章使用 GPT 4.0 和 Ai PDF 工具完成。 文章地址&#xff1a;https://arxiv.org/pdf/2402.13616.pdf 文章目录 文章简介有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f;论文试图解决什么问题&a…

实现律所高质量发展-Alpha法律智能操作系统

律师行业本质上属于服务行业&#xff0c;而律师团队作为一个独立的服务单位&#xff0c;应当包含研发、市场、销售、服务等单位发展的基础工作环节。但现实中&#xff0c;很多律师团队其实并没有区分这些工作。鉴于此&#xff0c;上海市锦天城律师事务所医药大健康行业资本市场…

2.22 day3、4 QT

完善对话框&#xff0c;点击登录对话框&#xff0c;如果账号和密码匹配&#xff0c;则弹出信息对话框&#xff0c;给出提示"登录成功”&#xff0c;提供一个Ok按钮&#xff0c;用户点击Ok后&#xff0c;关闭登录界面&#xff0c;跳转到其他界面 如果账号和密码不匹配&…

MIT-6.824-Lab2,Raft部分笔记|Use Go

文章目录 前记Paper6&#xff1a;RaftLEC5、6&#xff1a;RaftLAB22AtaskHintlockingstructureguide设计与编码 2BtaskHint设计与编码 2CtaskHint question后记 LEC5&#xff1a;GO, Threads, and Raftgo threads技巧raft实验易错点debug技巧 前记 趁着研一考完期末有点点空余…