0%

springboot数据脱敏

自定义Jackson数据脱敏

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

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

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.example.demo.example.annotation;


    import com.example.demo.example.SecretJsonConfig.SecretJsonSerializer;
    import com.example.demo.example.SecretJsonConfig.SecretStrategy;
    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();


    }

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

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    package com.example.demo.example.SecretJsonConfig;
    import lombok.Getter;

    import java.util.function.Function;

    /**
    * 脱敏策略,不同数据可选择不同的策略
    */
    @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;
    }


    }

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

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    package com.example.demo.example.SecretJsonConfig;

    import com.example.demo.example.annotation.SecretColumn;
    import com.example.demo.example.mapper.ConfigMapper;
    import com.fasterxml.jackson.core.JsonGenerator;
    import com.fasterxml.jackson.databind.BeanProperty;
    import com.fasterxml.jackson.databind.JsonMappingException;
    import com.fasterxml.jackson.databind.JsonSerializer;
    import com.fasterxml.jackson.databind.SerializerProvider;
    import com.fasterxml.jackson.databind.ser.ContextualSerializer;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;

    import java.io.IOException;
    import java.util.Objects;

    /**
    * 序列化器实现
    */
    //@Component//不加@Component注解,ConfigMapper无法注入
    public class SecretJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
    private SecretStrategy secretStrategy;

    //@Autowired
    //ConfigMapper configMapper;

    @Value("${secretSwitch:}")
    private String secretSwitch;

    /**
    * 步骤一
    * 方法来源于ContextualSerializer,获取属性上的注解属性,同时返回一个合适的序列化器
    */
    @Override
    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
    // 获取自定义注解
    SecretColumn annotation = beanProperty.getAnnotation(SecretColumn.class);

    //查询数据库脱敏开关状态(更改数据库之后会报null异常,暂时放弃此条件判断)
    //secretSwitch = configMapper.getSecretFlag();
    //脱敏开关打开时,才进行脱敏处理
    if(!Objects.isNull(secretSwitch) && "on".equals(secretSwitch)){
    // 注解不为空,且标注的字段为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));
    }
    }
    }

实体类中使用@SecretColumn注解

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    package com.example.demo.example.entity;

    import com.example.demo.example.SecretJsonConfig.SecretStrategy;
    import com.example.demo.example.annotation.SecretColumn;
    import lombok.Data;
    import lombok.ToString;
    import lombok.experimental.Accessors;

    @ToString
    @Data
    @Accessors(chain = true)
    public class UserEntity {

    /**
    * 真实姓名
    */
    @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;

    }

测试

  • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.example.demo.example;

    import com.example.demo.example.entity.UserEntity;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    @RequestMapping("/secret")
    public class TestController {

    @GetMapping("/test")
    public UserEntity test(){
    UserEntity user = new UserEntity();
    user.setRealName("张三(学生)")
    .setPhoneNumber("18000000000")
    .setAddress("蓝星地球村太平洋100号")
    .setIdCard("610000000000000000");
    // System.out.println(user);
    return user;
    }

    }

    测试结果

    • 结果

      1
      2
      3
      4
      5
      6
      7
      8
      // http://localhost:8080/secret/test

      {
      "realName": "张*(学生)",
      "address": "蓝星地****太平洋10****",
      "phoneNumber": "180****0000",
      "idCard": "6100****0000"
      }

    POM依赖

    • 代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>2.3.9.RELEASE</version>
      <relativePath/> <!-- lookup parent from repository -->
      </parent>

      <groupId>com.example</groupId>
      <artifactId>demo</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <name>demo</name>
      <description>Demo project for Spring Boot</description>
      <properties>
      <java.version>1.8</java.version>
      <spring-cloud.version>Hoxton.SR10</spring-cloud.version>
      </properties>
      <dependencies>

      <!--spring-boot-starter-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      </dependency>
      <!--spring-boot-starter-web-->
      <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      </dependency>

      <!-- lombok -->
      <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.30</version>
      <scope>provided</scope>
      </dependency>

      <!--mybatis plus-->
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.2</version>
      </dependency>

      <!--mysql数据库驱动-->
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
      </dependency>

      <!--dynamic多数据源-->
      <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
      <version>3.5.1</version>
      </dependency>

      </dependencies>
      <dependencyManagement>
      <dependencies>
      <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-dependencies</artifactId>
      <version>${spring-cloud.version}</version>
      <type>pom</type>
      <scope>import</scope>
      </dependency>
      </dependencies>
      </dependencyManagement>


      </project>

参考:

https://blog.csdn.net/Staba/article/details/125231376