使用mybatis或mybatisplus存储数据库加密,解密

博客介绍了Java开发中的注解使用,在类头添加@TableName注解指定表名并开启自动结果映射,在需加密字段添加@TableField注解并指定类型处理器。

/**
 * @author kxs
 * @email 1965654634@qq.com
 */
import com.chinaunicom.cpic.iot.dap.common.utils.AESUtils;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * @author xrp
 */
public class TypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, AESUtils.encrypt(parameter,"1234567891234567"));
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return AESUtils.decrypt(rs.getString(columnName),"1234567891234567");
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return AESUtils.decrypt(rs.getString(columnIndex),"1234567891234567");
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        return AESUtils.decrypt(cs.getString(columnIndex),"1234567891234567");
    }
}


import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import java.security.Key;
import java.security.SecureRandom;

/**
 * @ClassName AESUtils
 * @Description AES工具类
 */
@Slf4j
public class AESUtils {

    // optional value AES/DES/DESede
    public static final String CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";

    public static Key getKey(String strKey) {
        try {
            if (strKey == null) {
                strKey = "";
            }
            KeyGenerator _generator = KeyGenerator.getInstance("AES");
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(strKey.getBytes());
            _generator.init(128, secureRandom);
            return _generator.generateKey();
        } catch (Exception e) {
            throw new RuntimeException(" 密钥出现异常 ");
        }
    }

    public static String encrypt(String data, String key) {
        try {
            SecureRandom sr = new SecureRandom();
            Key secureKey = getKey(key);
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, secureKey, sr);
            byte[] bt = cipher.doFinal(data.getBytes());
            String strS = new BASE64Encoder().encode(bt);
            return strS;
        }catch (Exception e){
            log.error("加密失败,",e);
            throw new RuntimeException(802,"加密失败");
        }


    }

    public static String decrypt(String message,String key) {
        try {
            SecureRandom sr = new SecureRandom();
            Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
            Key secureKey = getKey(key);
            cipher.init(Cipher.DECRYPT_MODE, secureKey, sr);
            byte[] res = new BASE64Decoder().decodeBuffer(message);
            res = cipher.doFinal(res);
            return new String(res,"UTF-8");
        }catch (Exception e){
            log.error("解密失败,",e);
            throw new BusinessException(802,"解密失败");
        }

    }

}

在类头加上注解@TableName(value = “表名”,autoResultMap = true) ;
在需要加密的字段加上@TableField(typeHandler = TypeHandler.class)


``

<think>根据用户需求,需要实现Spring Boot与MyBatis-Plus结合实现数据自动加密存储解密读取,并在前端展示时脱敏。核心步骤包括: 1. 配置SM4加密算法(其他对称加密算法) 2. 利用MyBatis-Plus的插件机制(如拦截器)实现自动加密解密 3. 使用自定义注解和Jackson序列化实现数据脱敏 参考引用内容: [^1]提到配置Spring Boot支持SM4加密算法,并实现数据加密解密,结合MyBatis-Plus进行CRUD操作以及前端脱敏展示。 [^2]提到使用自定义注解(如`@FieldMask`)配合Jackson序列化实现脱敏。 具体实现步骤: 一、配置加密算法(以SM4为例) 创建SM4加密工具类,提供加密解密方法。 二、MyBatis-Plus自动加密解密 1. 使用MyBatis-Plus的`TypeHandler`对特定字段进行加密解密处理。 2. 使用拦截器(Interceptor)在SQL执行前后对参数和结果集进行处理。 三、数据脱敏显示 1. 定义脱敏注解(如`@Mask`),可指定脱敏类型(如手机号、身份证等)。 2. 实现Jackson的`JsonSerializer`,根据注解对字段值进行脱敏处理。 下面详细展开: 步骤1:加密工具类(SM4Util.java) ```java import org.bouncycastle.jce.provider.BouncyCastleProvider; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.security.Security; import java.util.Base64; public class SM4Util { static { Security.addProvider(new BouncyCastleProvider()); } private static final String ALGORITHM_NAME = "SM4"; private static final String ALGORITHM_ECB_PADDING = "SM4/ECB/PKCS5Padding"; public static String encrypt(String content, String key) throws Exception { byte[] keyBytes = key.getBytes(); SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher = Cipher.getInstance(ALGORITHM_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encrypted = cipher.doFinal(content.getBytes()); return Base64.getEncoder().encodeToString(encrypted); } public static String decrypt(String content, String key) throws Exception { byte[] keyBytes = key.getBytes(); SecretKeySpec secretKey = new SecretKeySpec(keyBytes, ALGORITHM_NAME); Cipher cipher = Cipher.getInstance(ALGORITHM_ECB_PADDING, BouncyCastleProvider.PROVIDER_NAME); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decrypted = cipher.doFinal(Base64.getDecoder().decode(content)); return new String(decrypted); } } ``` 步骤2:定义加密字段注解(用于实体类字段标记) ```java import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface EncryptField { } ``` 步骤3:实现MyBatis-Plus的`TypeHandler`处理加密字段 ```java import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class EncryptTypeHandler extends BaseTypeHandler<String> { private static final String SECRET_KEY = "your-secret-key-16byte"; // 16字节密钥 @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { try { String encrypted = SM4Util.encrypt(parameter, SECRET_KEY); ps.setString(i, encrypted); } catch (Exception e) { throw new SQLException("加密失败", e); } } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String encrypted = rs.getString(columnName); if (encrypted == null) return null; try { return SM4Util.decrypt(encrypted, SECRET_KEY); } catch (Exception e) { throw new SQLException("解密失败", e); } } // 其他方法类似... } ``` 步骤4:在实体类字段上使用`@EncryptField`注解并指定TypeHandler ```java import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import org.apache.ibatis.type.TypeHandler; @TableName("user") public class User { private Long id; @TableField(typeHandler = EncryptTypeHandler.class) @EncryptField private String phone; // 其他字段... } ``` 步骤5:脱敏处理 定义脱敏注解: ```java import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = MaskSerializer.class) public @interface Mask { MaskType value(); } public enum MaskType { PHONE, ID_CARD, NAME } ``` 实现脱敏序列化器: ```java import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; public class MaskSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { Mask mask = gen.getCurrentValue().getClass().getDeclaredField(gen.getOutputContext().getCurrentName()).getAnnotation(Mask.class); if (mask == null) { gen.writeString(value); return; } String result = value; switch (mask.value()) { case PHONE: result = value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); break; case ID_CARD: result = value.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1**********$2"); break; case NAME: result = value.length() <= 1 ? value : value.charAt(0) + "*" + (value.length() > 2 ? value.charAt(value.length()-1) : ""); break; default: break; } gen.writeString(result); } } ``` 在实体类中使用脱敏注解: ```java public class UserVO { @Mask(MaskType.PHONE) private String phone; // 其他字段... } ``` 注意:脱敏一般在返回给前端的VO对象上使用,避免直接使用实体类(因为实体类包含明文数据)。 步骤6:前端展示 后端返回的VO对象中,脱敏字段已经被处理,前端直接展示即可。 总结: 1. 数据存储时,通过MyBatis-Plus的TypeHandler对标记字段自动加密。 2. 数据查询时,TypeHandler自动解密。 3. 返回前端时,通过Jackson序列化对VO中的脱敏字段进行脱敏处理。 这样,实现了从存储到展示的全流程数据安全处理。 注意事项: - 密钥管理:实际项目中,密钥需要安全存储(如使用配置中心、KMS等),不能硬编码。 - 性能:加密解密会带来一定的性能损耗,需评估。 相关问题: 1. 如何实现非对称加密(如SM2)在Spring Boot中的集成? 2. MyBatis-Plus除了TypeHandler,还有哪些方式可以实现自动加密解密? 3. 如何在前端实现部分数据的加密传输? 4. 脱敏规则如何支持自定义(如自定义正则表达式)? 5. 如何实现加密字段的模糊查询? </think>### Spring Boot与MyBatis-Plus实现自动加密解密并脱敏显示的方法 以下是完整的实现方案,基于Spring Boot + MyBatis-Plus + Jackson的技术栈,实现数据存储自动加密、查询自动解密、返回前端自动脱敏的三层安全处理: --- ### 一、整体架构 ```mermaid graph TD A[前端请求] --> B(Controller) B --> C[Service] C --> D[MyBatis-Plus] D -->|插入/更新时| E[字段加密] D -->|查询时| F[字段解密] G[返回结果] --> H[Jackson序列化] H -->|脱敏处理| I[前端显示] ``` --- ### 二、核心实现步骤 #### 1. 加密算法配置(SM4示例) ```java // SM4加密工具类 @Component public class Sm4Crypto { private static final String SECRET_KEY = "your-16byte-secret"; // 实际项目需从安全配置读取 public String encrypt(String plainText) { // SM4加密实现(需引入BouncyCastle依赖) // 返回Base64编码的加密结果 } public String decrypt(String cipherText) { // SM4解密实现 } } ``` #### 2. MyBatis-Plus自动加解密使用TypeHandler) ```java // 字段加密处理器 public class EncryptTypeHandler extends BaseTypeHandler<String> { @Autowired private Sm4Crypto sm4Crypto; // 通过配置注入 @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) { ps.setString(i, sm4Crypto.encrypt(parameter)); // 写入时加密 } @Override public String getNullableResult(ResultSet rs, String columnName) { return sm4Crypto.decrypt(rs.getString(columnName)); // 读取时解密 } // 其他重载方法... } ``` #### 3. 实体类配置 ```java @Data @TableName("user_table") public class User { private Long id; @TableField(typeHandler = EncryptTypeHandler.class) private String phone; // 自动加解密的字段 private String name; } ``` #### 4. 脱敏注解定义 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JsonSerialize(using = MaskSerializer.class) // 指定序列化器 public @interface Mask { MaskType value() default MaskType.DEFAULT; } public enum MaskType { PHONE, ID_CARD, EMAIL, BANK_CARD, DEFAULT } ``` #### 5. 脱敏序列化器 ```java public class MaskSerializer extends JsonSerializer<String> { @Override public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException { Mask mask = gen.getContext().getAnnotation(Mask.class); gen.writeString(applyMask(value, mask != null ? mask.value() : MaskType.DEFAULT)); } private String applyMask(String value, MaskType type) { if (StringUtils.isBlank(value)) return ""; switch (type) { case PHONE: // 手机号:138****1234 return value.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"); case ID_CARD: // 身份证:110**********123X return value.replaceAll("(\\d{3})\\d{12}([0-9X]{1})", "$1************$2"); case EMAIL: // 邮箱:t***@example.com return value.replaceAll("(.).*@(.*)", "$1***@$2"); default: return value; // 默认脱敏规则 } } } ``` #### 6. DTO对象脱敏应用 ```java @Data public class UserDTO { private Long id; @Mask(MaskType.PHONE) private String phone; // 返回前端时自动脱敏 private String name; } ``` #### 7. Service层转换 ```java @Service public class UserService { public UserDTO getUserById(Long id) { User entity = userMapper.selectById(id); return convertToDTO(entity); } private UserDTO convertToDTO(User user) { // 使用BeanUtilsMapStruct转换 UserDTO dto = new UserDTO(); dto.setPhone(user.getPhone()); // 此处phone已是解密状态 return dto; } } ``` --- ### 三、关键配置说明 1. **MyBatis配置扫描TypeHandler** ```yaml mybatis-plus: type-handlers-package: com.example.handler ``` 2. **加密密钥管理** - 生产环境应从安全配置中心获取 - 使用`jasypt-spring-boot`实现配置项加密 3. **注意事项** - 加解密操作会引入性能损耗,建议异步处理硬件加速 - 脱敏规则需符合《个人信息保护法》要求 - 加密字段不支持模糊查询,需额外设计搜索方案[^1][^2] --- ### 四、扩展优化建议 1. **自定义注解实现存储加密** ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface EncryptField {} // 通过MyBatis拦截器自动处理注解字段 ``` 2. **缓存优化** 对高频访问的非敏感数据,使用`@Cacheable`缓存解密结果 3. **动态脱敏** ```java @Mask(expression = "T(com.example.MaskUtil).dynamicMask(#role)") private String bankCard; // 根据用户角色动态脱敏 ``` 4. **审计日志** 结合`@EncryptField`和`@Mask`实现日志差异化输出 --- ### 五、常见问题解决方案 **问题1:加解密字段如何参与查询?** - 精确查询:先加密参数再查询 - 模糊查询:需设计脱敏索引使用`SQL函数`: ```sql SELECT * FROM table WHERE DECRYPT(column) LIKE CONCAT('%', #{param}, '%') ``` **问题2:多端差异化脱敏** 通过自定义`@ConditionalMask`实现: ```java @Mask(condition = "!T(com.example.SecurityUtil).isInternalUser()") private String idCard; ``` **问题3:大数据量性能优化** - 使用批处理加解密 - 启用数据库硬件加密(如MySQL透明加密) - 敏感字段与非敏感字段分离存储
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值