SpringBoot接口如何对返回数据进行脱敏(Mybatis、Jackson)呢?

重生 SpringBoot 发布时间:2024-02-03 08:40:31 阅读数:18841 1
下文笔者讲述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"}
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaFramework/SpringBoot/202402/7896.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者