当前位置:   article > 正文

SpringBoot接口返回数据脱敏(Mybatis、Jackson)_springboot 电话脱敏

springboot 电话脱敏

一、前言

有时候,我们接口返回的数据需要做一些处理,有一些敏感数据,我们不能全部返回给用户,需要用*号隐藏掉一部分关键数据,使得该敏感数据变得不完全,其他人无法知道脱敏前的数据是什么样的。同时,存储在底层数据库的数据,一些关键信息如用户密码、身份证、手机号等敏感信息,也不能够通过明文的方式存放在数据库中。

数据脱敏有以下几种做法:

1、通过Mybatis处理

2、通过自定义Jackson注解,实现在属性序列化过程中处理数据

3、其他方式

二、Mybatis数据脱敏

在数据库中,根据业务需求存放了不少的有关用户的敏感信息,比如身份证、手机、住址、密码等信息,如果这些数据都是以明文的形式存放的,那么,当数据库被破解后,用户这些重要的信息都会被泄露。

数据加密解密很简单,如果自己手动在插入数据前对数据加密,读取到数据后进行手动解密,那么将会很麻烦。

因此,对于数据库数据的加密解密将采用Mybatis的TypeHandler处理,在我们为数据库提供数据后,会根据我们的需求,对部分数据进行加密。在读取后数据最终到我们手上前,会对读取到数据进行解密。请给位跟随以下的做法,实现Mybatis数据脱敏把。

1、自定义一个TypeHandler类型的处理器,用于处理数据的加密和解密

TypeHandler用于处理数据在数据库类型和java类型之间的转换,默认情况下,我们常用的String、Long、Date等java类型都有对应的TypeHandler进行处理,我们自定义TypeHanler的情况在于目前没有对应的数据转换处理器。

以下为自定义的字符串加密解密处理器:

  1. import cn.hutool.crypto.symmetric.AES;
  2. import org.apache.ibatis.type.BaseTypeHandler;
  3. import org.apache.ibatis.type.JdbcType;
  4. import org.springframework.util.StringUtils;
  5. import java.nio.charset.StandardCharsets;
  6. import java.sql.CallableStatement;
  7. import java.sql.PreparedStatement;
  8. import java.sql.ResultSet;
  9. import java.sql.SQLException;
  10. import java.util.Objects;
  11. /**
  12. * mybatis String类型敏感字段处理类
  13. * 可以通过实现TypeHandler,但是BaseTypeHandler已经实现了,我们可以继承它
  14. */
  15. public class SensitiveColumnHandler extends BaseTypeHandler<String> {
  16. private static final String key = "wjfgncvkdkd25fc2";
  17. /**
  18. * 设置参数值,在此处对字段值进行加密处理
  19. */
  20. @Override
  21. public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
  22. // 如果字段或字段值为空,不进行处理
  23. if(StringUtils.isEmpty(parameter)){
  24. ps.setString(i, parameter);
  25. return;
  26. }
  27. // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可
  28. AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8));
  29. String secretStr = aes.encryptHex(parameter);
  30. ps.setString(i, secretStr);
  31. }
  32. /**
  33. * 获取值内容
  34. */
  35. @Override
  36. public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
  37. String value = rs.getString(columnName);
  38. if(Objects.isNull(value)){
  39. return null;
  40. }
  41. // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可
  42. AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8));
  43. return aes.decryptStr(value);
  44. }
  45. /**
  46. * 获取值内容
  47. */
  48. @Override
  49. public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
  50. String value = rs.getString(columnIndex);
  51. if(Objects.isNull(value)){
  52. return null;
  53. }
  54. // 对字段值就行加密,此处使用hutool工具,有其他使用其他即可
  55. AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8));
  56. return aes.decryptStr(value);
  57. }
  58. /**
  59. * 获取值内容
  60. */
  61. @Override
  62. public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
  63. String value = cs.getString(columnIndex);
  64. if(Objects.isNull(value)){
  65. return null;
  66. }
  67. // 对字段值进行加密,此处使用hutool工具,有其他使用其他即可
  68. AES aes = SecureUtil.aes(key.getBytes(StandardCharsets.UTF_8));
  69. return aes.decryptStr(value);
  70. }
  71. }

以上就是我们自定义一个TypeHandler类型的处理器,以下是关于处理器的使用,有分以下两种情况:

1)、全局使用

全局使用需要在配置文件中指定处理器包位置,指定之后,在默认情况下,遇到该处理器能够处理的类型,都将使用该处理器。不建议使用全局的方式使用自定义处理器,比如本文我们的自定义处理器是用于处理String字符串的,全局注册处理器之后,所有的String值将会使用该处理器。但实际情况是,只有在字符串是敏感数据时,我们才需要用到自定义的处理器。

  1. #指定TypeHandler处理器的包位置
  2. type-handlers-package: com.sensitive.learn.handler

2)、局部使用

在我们需要的字段上使用typeHandler表明指定的处理器,没有标注typeHandler的字段,将采用默认的处理器。(此处使用见Mapper.xml)。

2、创建Test实体类

  1. @Data
  2. @Accessors(chain = true)
  3. public class Test {
  4. private Long id;
  5. private String idCard;
  6. private String phone;
  7. }

3、创建TestMapper接口类

  1. @Mapper
  2. public interface TestMapper {
  3. List<Test> selectAll();
  4. Boolean insert(Test test);
  5. }

4、Myatis Mapper文件,在需要自定义TypeHandler的字段是使用typeHandler属性进行指定

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE mapper
  3. PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5. <mapper namespace="com.sensitive.learn.mapper.TestMapper">
  6. <resultMap id="testMap" type="com.sensitive.learn.model.Test">
  7. <id property="id" column="id"/>
  8. <result property="idCard" column="id_card" typeHandler="com.sensitive.learn.handler.SensitiveColumnHandler"/>
  9. <result property="phone" column="phone" typeHandler="com.sensitive.learn.handler.SensitiveColumnHandler"/>
  10. </resultMap>
  11. <select id="selectAll" resultMap="testMap">
  12. select id, id_card, phone
  13. from test
  14. </select>
  15. <insert id="insert" parameterType="com.sensitive.learn.model.Test">
  16. insert into test(id, id_card, phone)
  17. values(#{id},
  18. #{idCard, typeHandler=com.sensitive.learn.handler.SensitiveColumnHandler},
  19. #{phone, typeHandler=com.sensitive.learn.handler.SensitiveColumnHandler})
  20. </insert>
  21. </mapper>

5、测试插入与查询

  1. @SpringBootTest
  2. @RunWith(SpringRunner.class)
  3. public class TestClass {
  4. @Resource
  5. private TestMapper testMapper;
  6. @Test
  7. public void test1(){
  8. List<com.sensitive.learn.model.Test> tests = testMapper.selectAll();
  9. tests.forEach(System.out::println);
  10. }
  11. @Test
  12. public void test2(){
  13. com.sensitive.learn.model.Test test = new com.sensitive.learn.model.Test();
  14. test.setId(6L)
  15. .setIdCard("4493888665464654660")
  16. .setPhone("1234567890");
  17. testMapper.insert(test);
  18. }
  19. }

插如之后查看数据库表情况:

 可以看出数据已经经过加密了

查询控制台打印的结果:

Test(id=6, idCard=4493888665464654660, phone=1234567890)

可以看出能够正常解密

好了,以上有关Mybatis的数据脱敏就到此为止了。

三、自定义Jackson数据脱敏

Jackson是Spring默认的序列化框架,以下将通过自定义Jackson注解,实现在序列化过程中对属性值进行处理。

1、定义一个注解,标注在需要脱敏的字段上

  1. import com.boot.learn.enums.SecretStrategy;
  2. import com.boot.learn.serializer.SecretJsonSerializer;
  3. import com.fasterxml.jackson.annotation.JacksonAnnotationsInside;
  4. import com.fasterxml.jackson.databind.annotation.JsonSerialize;
  5. import java.lang.annotation.ElementType;
  6. import java.lang.annotation.Retention;
  7. import java.lang.annotation.RetentionPolicy;
  8. import java.lang.annotation.Target;
  9. @Target(ElementType.FIELD) // 标注在字段上
  10. @Retention(RetentionPolicy.RUNTIME)
  11. @JacksonAnnotationsInside // 一般用于将其他的注解一起打包成"组合"注解
  12. @JsonSerialize(using = SecretJsonSerializer.class) // 对标注注解的字段采用哪种序列化器进行序列化
  13. public @interface SecretColumn {
  14. // 脱敏策略
  15. SecretStrategy strategy();
  16. }

2、定义字段序列化策略,因为不同类型数据有不同脱敏后的展现形式。以下通过枚举类方式实现几种策略:

  1. /**
  2. * 脱敏策略,不同数据可选择不同的策略
  3. */
  4. @Getter
  5. public enum SecretStrategy {
  6. /**
  7. * 用户名脱敏
  8. */
  9. USERNAME(str -> str.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
  10. /**
  11. * 身份证脱敏
  12. */
  13. ID_CARD(str -> str.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
  14. /**
  15. * 手机号脱敏
  16. */
  17. PHONE(str -> str.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
  18. /**
  19. * 地址脱敏
  20. */
  21. ADDRESS(str -> str.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
  22. private final Function<String, String> desensitizer;
  23. SecretStrategy(Function<String, String> desensitizer){
  24. this.desensitizer = desensitizer;
  25. }
  26. }

3、定义一个Jackson序列化器,可以对标注了@SecretColumn 的注解进行处理

  1. /**
  2. * 序列化器实现
  3. */
  4. public class SecretJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
  5. private SecretStrategy secretStrategy;
  6. /**
  7. * 步骤一
  8. * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
  9. */
  10. @Override
  11. public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
  12. // 获取自定义注解
  13. SecretColumn annotation = beanProperty.getAnnotation(SecretColumn.class);
  14. // 注解不为空,且标注的字段为String
  15. if(Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())){
  16. this.secretStrategy = annotation.strategy();
  17. // 符合我们自定义情况,返回本序列化器,将顺利进入到该类中的serialize()方法中
  18. return this;
  19. }
  20. // 注解为空,字段不为String,寻找合适的序列化器进行处理
  21. return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);
  22. }
  23. /**
  24. * 步骤二
  25. * 方法来源于JsonSerializer<String>:指定返回类型为String类型,serialize()将修改后的数据返回
  26. */
  27. @Override
  28. public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
  29. if(Objects.isNull(secretStrategy)){
  30. // 定义策略为空,返回原字符串
  31. jsonGenerator.writeString(s);
  32. }else {
  33. // 定义策略不为空,返回策略处理过的字符串
  34. jsonGenerator.writeString(secretStrategy.getDesensitizer().apply(s));
  35. }
  36. }
  37. }

4、实体类中使用@SecretColumn注解

  1. @ToString
  2. @Data
  3. @Accessors(chain = true)
  4. public class User {
  5. /**
  6. * 真实姓名
  7. */
  8. @SecretColumn(strategy = SecretStrategy.USERNAME)
  9. private String realName;
  10. /**
  11. * 地址
  12. */
  13. @SecretColumn(strategy = SecretStrategy.ADDRESS)
  14. private String address;
  15. /**
  16. * 电话号码
  17. */
  18. @SecretColumn(strategy = SecretStrategy.PHONE)
  19. private String phoneNumber;
  20. /**
  21. * 身份证号码
  22. */
  23. @SecretColumn(strategy = SecretStrategy.ID_CARD)
  24. private String idCard;
  25. }

5、测试

  1. @RestController
  2. @RequestMapping("/secret")
  3. public class SecretController {
  4. @GetMapping("/test")
  5. public User test(){
  6. User user = new User();
  7. user.setRealName("陈平安")
  8. .setPhoneNumber("12345678910")
  9. .setAddress("浩然天下宝瓶洲骊珠洞天泥瓶巷")
  10. .setIdCard("4493888665464654659");
  11. System.out.println(user);
  12. return user;
  13. }
  14. }

输出结果:

1)控制台打印结果

User(realName=陈平安, address=浩然天下宝瓶洲骊珠洞天泥瓶巷, phoneNumber=12345678910, idCard=4493888665464654659)

2)浏览器结果

{"realName":"陈*安","address":"浩然天****瓶洲骊珠洞天泥****","phoneNumber":"123****8910","idCard":"4493****54659"}

结论:通过自定义Jackson实现数据脱敏,猜测生效的过程是在接口返回对象数据时,序列化器将对象数据转换成json数据时实现的。

可以在代码中通过ObjectMapper序列化对象,同样能够得到脱敏后的数据:

  1. ObjectMapper objectMapper = new ObjectMapper();
  2. try {
  3. System.out.println(objectMapper.writeValueAsString(user));
  4. } catch (JsonProcessingException e) {
  5. e.printStackTrace();
  6. }

控制台打印结果:

{"realName":"陈*安","address":"浩然天****瓶洲骊珠洞天泥****","phoneNumber":"123****8910","idCard":"4493****54659"}

注意:如果使用的不是Jackson,而是fastjson或者其他的序列化工具,则需要使用定义对应组件能够识别的序列化器,否则,序列化将不生效。

四、总结

以上是关于接口数据脱敏两个方面的实现,一方面是通过Mybatis实现,一方面是通过Jackson实现,大家可以通过自己的业务需求灵活组合使用,事半功倍!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/代码探险家/article/detail/920838
推荐阅读
相关标签
  

闽ICP备14008679号