SpringBoot接口如何对返回数据进行脱敏(Mybatis、Jackson)呢?
下文笔者讲述SpringBoot对返回数据脱敏的方法及示例分享
在日常开发中,经常有一个敏感数据需禁止在页面显示 那么此时SpringBoot中有没有一种快捷方式 可以使我们非常方便的对数据进行脱敏操作呢? 下文笔者将一一道来,如下所示
方式1.使用Mybatis对数据进行脱敏操作 方式2.自定义Jackson注解的方式 对指定属性进行脱敏 方式3.其他方式,如:自定义脱敏方式等
Mybatis数据脱敏
数据库中会存储用户的身份证 手机号码、住址、密码等非常重要的信息 此时我们最好在数据插入时,对其进行加密,数据取回时进行自动解密 那么这种业务场景在mybatis中,则非常容易处理,我们只需编写相应的TypeHandler处理即可例:自定义TypeHandler对数据进行加密和解密操作
import cn.hutool.crypto.symmetric.AES; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.springframework.util.StringUtils; import java.nio.charset.StandardCharsets; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Objects; /** * mybatis String类型敏感字段处理类 * 可以通过实现TypeHandler,但是BaseTypeHandler已经实现了,我们可以继承它 */ public class SensitiveColumnHandler extends BaseTypeHandler<String> { private static final String key = "wjfgncvkdkd25fc2"; /** * 设置参数值,在此处对字段值进行加密处理 */ @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { // 如果字段或字段值为空,不进行处理 if(StringUtils.isEmpty(parameter)){ ps.setString(i, parameter); return; } // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可 AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8)); String secretStr = aes.encryptHex(parameter); ps.setString(i, secretStr); } /** * 获取值内容 */ @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); if(Objects.isNull(value)){ return null; } // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可 AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8)); return aes.decryptStr(value); } /** * 获取值内容 */ @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { String value = rs.getString(columnIndex); if(Objects.isNull(value)){ return null; } // 对字段值就行加密,此处使用hutool工具,有其他使用其他即可 AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8)); return aes.decryptStr(value); } /** * 获取值内容 */ @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { String value = cs.getString(columnIndex); if(Objects.isNull(value)){ return null; } // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可 AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8)); return aes.decryptStr(value); } } 1.全局使用 全局使用 需要在配置文件中指定处理器包位置 指定之后 在默认情况下 遇到该处理器能够处理的类型 都将使用该处理器 笔者不建议使用全局的方式使用自定义处理器 如: 本文我们的自定义处理器是用于处理String字符串 全局注册处理器之后 所有的String值将会使用该处理器,但日常其实我们只需对敏感数据进行脱敏处理 #指定TypeHandler处理器的包位置 type-handlers-package: com.sensitive.learn.handler 2.局部使用 在需要脱敏的字段上使用typeHandler 指定的处理器 没有标注typeHandler的字段 将采用默认的处理器(Mapper.xml) 2.创建Test实体类 @Data @Accessors(chain = true) public class Test { private Long id; private String idCard; private String phone; } 3.创建TestMapper接口类 @Mapper public interface TestMapper { list<Test> selectAll(); Boolean insert(Test test); } 4.Myatis Mapper文件 自定义TypeHandler字段 实现局部字段脱敏 <?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="com.sensitive.learn.mapper.TestMapper"> <resultMap id="testMap" type="com.sensitive.learn.model.Test"> <id property="id" column="id"/> <result property="idCard" column="id_card" typeHandler="com.sensitive.learn.handler.SensitiveColumnHandler"/> <result property="phone" column="phone" typeHandler="com.sensitive.learn.handler.SensitiveColumnHandler"/> </resultMap> <select id="selectAll" resultMap="testMap"> select id, id_card, phone from test </select> <insert id="insert" parameterType="com.sensitive.learn.model.Test"> insert into test(id, id_card, phone) values(#{id}, #{idCard, typeHandler=com.sensitive.learn.handler.SensitiveColumnHandler}, #{phone, typeHandler=com.sensitive.learn.handler.SensitiveColumnHandler}) </insert> </mapper> 5.测试插入与查询 @SpringBootTest @RunWith(SpringRunner.class) public class TestClass { @Resource private TestMapper testMapper; @Test public void test1(){ List<com.sensitive.learn.model.Test> tests = testMapper.selectAll(); tests.forEach(System.out::println); } @Test public void test2(){ com.sensitive.learn.model.Test test = new com.sensitive.learn.model.Test(); test.setId(6L) .setIdCard("77232391") .setPhone("8965"); testMapper.insert(test); } } Test(id=6, idCard=77232391, phone=8965)
Jackson数据脱敏
Jackson 是Spring默认序列化框架 以下将通过自定义Jackson注解 实现在序列化过程中对属性值进行处理例:Jackson数据脱敏的示例
1.定义一个注解 标注在需要脱敏的字段上 import com.boot.learn.enums.SecretStrategy; import com.boot.learn.serializer.SecretJsonSerializer; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.FIELD) // 标注在字段上 @Retention(RetentionPolicy.Runtime) @JacksonAnnotationsInside // 一般用于将其他的注解一起打包成"组合"注解 @JsonSerialize(using = SecretJsonSerializer.class) // 对标注注解的字段采用哪种序列化器进行序列化 public @interface SecretColumn { // 脱敏策略 SecretStrategy strategy(); } 2、定义字段序列化策略,因为不同类型数据有不同脱敏后的展现形式。以下通过枚举类方式实现几种策略: /** * 脱敏策略,不同数据可选择不同的策略 */ @Getter public enum SecretStrategy { /** * 用户名脱敏 */ USERNAME(str -> str.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), /** * 身份证脱敏 */ ID_CARD(str -> str.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")), /** * 手机号脱敏 */ PHONE(str -> str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), /** * 地址脱敏 */ ADDRESS(str -> str.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****")); private final Function<String, String> desensitizer; SecretStrategy(Function<String, String> desensitizer){ this.desensitizer = desensitizer; } } 3.定义一个Jackson序列化器,可以对标注了@SecretColumn 的注解进行处理 /** * 序列化器实现 */ public class SecretJsonSerializer extends JsonSerializer<String> implements ContextualSerializer { private SecretStrategy secretStrategy; /** * 步骤一 * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器 */ @Override public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException { // 获取自定义注解 SecretColumn annotation = beanProperty.getAnnotation(SecretColumn.class); // 注解不为空,且标注的字段为String if(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){ this.secretStrategy = annotation.strategy(); // 符合我们自定义情况,返回本序列化器,将顺利进入到该类中的serialize()方法中 return this; } // 注解为空,字段不为String,寻找合适的序列化器进行处理 return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } /** * 步骤二 * 方法来源于JsonSerializer<String>:指定返回类型为String类型,serialize()将修改后的数据返回 */ @Override public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { if(Objects.isNull(secretStrategy)){ // 定义策略为空,返回原字符串 jsonGenerator.writeString(s); }else { // 定义策略不为空,返回策略处理过的字符串 jsonGenerator.writeString(secretStrategy.getDesensitizer().apply(s)); } } } 4.实体类中使用@SecretColumn注解 @ToString @Data @Accessors(chain = true) public class User { /** * 真实姓名 */ @SecretColumn(strategy = SecretStrategy.USERNAME) private String realName; /** * 地址 */ @SecretColumn(strategy = SecretStrategy.ADDRESS) private String address; /** * 电话号码 */ @SecretColumn(strategy = SecretStrategy.PHONE) private String phoneNumber; /** * 身份证号码 */ @SecretColumn(strategy = SecretStrategy.ID_CARD) private String idCard; } 5、测试 @RestController @RequestMapping("/secret") public class SecretController { @GetMapping("/test") public User test(){ User user = new User(); user.setRealName("猫猫小屋") .setPhoneNumber("8889") .setAddress("月球村") .setIdCard("71232424242424242442"); System.out.println(user); return user; } } ---运行以上代码,将输出以下信息 User(realName=猫猫小屋, address=月球村, phoneNumber=8889, idCard=71232424242424242442) ---脱敏效果 {"realName":"猫**屋","address":"***","phoneNumber":"88*9","idCard":"71232424*****42442"}
版权声明
本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。