需求分析
- 数据脱敏就是将数据展示进行转换用我们的脱敏符号展示,例如手机号1234,前端展示为1**4,常见的还有身份证,用户密码。
注意点
- 数据脱敏不是数据加密,数据库的数据不是1**4这种,还是正常数据,我们只是再和前端展示的给前端传递的这种json脱敏后的数据
- 疑问点:也就是若是前端我们还要往后端传输数据怎么办呢(脱敏用的一套只是用于展示数据的)?我们后端有两个东西一个是正常的entity实体类,一个是我们集成的要展示数据的Vo,前端要往后端流转数据接口返回的是实体类那套,前端脱敏展现我们接口返回使用的Vo这种。
技术分析
- 我们先想清楚我们可以再什么层次可以进行数据脱敏处理
- 流程:前端发送请求->后端接收->请求数据库->返回给后端->后端返回给前端
- 所以我们就可以再3个层次进行脱敏处理,
- 1是在在sql层面进行处理,我们查询数据后然后再修改为我们的脱敏数据;
- 2是我们后端查询出数据后然后遍历后端进行处理为脱敏数据然后返回给前端
- 3是我们后端返回给前端的是json数据,然后在转换为json的时候将其转为脱敏数据的json不就好了(java的Jackson专门用于处理json)
方案
- sql层脱敏数据
- 后端层处理sql查询处理的数据再进行脱敏数据
- 后端层在返回json进行脱敏数据(Jackson),对json进行序列化,自定义注解(多种类型序列化)
实现案例
对时间进行序列化JsonFormat
数字转为字符串序列化
自定义注解集成序列化,
我们要是一个一个的用序列化器那就很麻烦了,所以我们使用一个自定义注解将各种序列化规则集成一下
步骤
引入依赖(Jackson就在springboot里)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2、使用@JacksonAnnotationsInside注解和@JsonSerialize自定义注解
- @JacksonAnnotationsInside注解的功能:
- 注解不关注下面的类,而是上面的注解,我们就是要使用@JsonSerialize( using = DesensitizeSerialize.class)来进行序列化器。我们想要强调是这个
- 其表明是JackSon这里包下面的处理json的。
package com.example.demo.annotation;
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.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
//JacksonAnnotationsInside 组合注解
@JacksonAnnotationsInside
// 序列化器
@JsonSerialize( using = DesensitizeSerialize.class)
public @interface JsonDesensitize {
/**
* 脱敏类型
*/
DesensitizeType type();
/**
* 脱敏字符
*/
String symbol() default "*";
/**
* 脱敏长度
*/
int length() default -1;
/**
* 自定义脱敏规则(正则表达式)
*/
String regex() default "";
/**
* 脱敏规则组
*/
int group() default 0;
}
3、创建实体类
package com.example.demo.entity;
import com.example.demo.annotation.DesensitizeType;
import com.example.demo.annotation.JsonDesensitize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
/**
* ID
*/
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 密码
*/
@JsonDesensitize(type = DesensitizeType.PASSWORD)
private String passWord;
/**
* 电话
*/
@JsonDesensitize(type = DesensitizeType.TEL)
private String tel;
/**
* 邮箱
*/
@JsonDesensitize(type = DesensitizeType.EMAIL)
private String email;
/**
* 身份证
*/
@JsonDesensitize(type = DesensitizeType.ID_CARD)
private String idCard;
}
4、创建控制层测试
package com.example.demo.controller;
import com.example.demo.entity.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class TestDemo {
@GetMapping("/testDemo")
public User testDemo(){
return User.builder()
.id(1)
.name("张三")
.passWord("1234567879")
.tel("13888884328")
.email("495910871@qq.com")
.idCard("533321190103253910")
.build();
}
}
5、创建字段类型枚举并设置对应字段的序列化器规则(脱敏)
package com.example.demo.annotation;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 脱敏类型
*/
public enum DesensitizeType {
TEL, PASSWORD, EMAIL, ID_CARD, CUSTOM;
/**
* 手机号脱敏(中间四位脱敏)
* @param tel 手机号
* @param symbol 脱敏符号
* @return
*/
public static String tel(String tel, String symbol){
// 手机号开头为1的
if(tel.matches("^1\\d{10}$")){
StringBuilder replacedPart = new StringBuilder();
for (int i = 0; i < 4; i++) {
replacedPart.append(symbol);
}
return tel.substring(0, 3) + replacedPart.toString() + tel.substring(7);
}
// 不符合直接返回
return tel;
}
/**
* 密码脱敏(全部脱敏)
* @param passWord 密码
* @param symbol 脱敏符号
* @return
*/
public static String passWord(String passWord, String symbol ){
StringBuilder desensitizePassWord = new StringBuilder();
if(passWord != null){
for (int i = 0; i < passWord.length(); i++) {
desensitizePassWord.append(symbol);
}
return desensitizePassWord.toString();
}
return passWord;
}
/**
* 邮箱脱敏(@前面脱敏)
* @param email 邮箱
* @param symbol 脱敏符号
* @return
*/
public static String email(String email, String symbol){
StringBuilder replacedPart = new StringBuilder();
if(email != null && email.contains("@")){
int atIndex = email.indexOf("@");
for(int i = 0; i < atIndex; i++ ){
replacedPart .append(symbol);
}
// 前面的数字脱敏后再加上@后面的
return replacedPart.toString() + email.substring(atIndex);
}
return email;
}
/**
* 身份证号脱敏(第一位和最后一位之间脱敏)
* @param idCard 身份证号
* @param symbol 脱敏符号
* @return
*/
public static String idCard(String idCard, String symbol){
StringBuilder replacedPart = new StringBuilder();
String regex = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[1-2]\\d|3[0-1])\\d{3}([0-9Xx])$";
if(idCard != null && idCard.matches(regex)){
for(int i = 1; i < idCard.length()-1; i++){
replacedPart.append(symbol);
}
return idCard.substring(0,1) + replacedPart.toString() + idCard.substring(idCard.length() -1);
}
return idCard;
}
/**
* 自定义脱敏规则
* @param value 待脱敏值
* @param symbol 脱敏符号
* @param regex 脱敏规则
* @param length 脱敏长度
* @param group 脱敏组
* @return
*/
private static String custom(String value, String symbol, String regex, int length,int group){
Pattern compile = Pattern.compile(regex);
Matcher matcher = compile.matcher(value);
StringBuilder replacedPart = new StringBuilder();
if(value != null && matcher.find()){
int start = matcher.start(group);
int end = matcher.end();
for (int i = 0; i < length; i++) {
replacedPart.append(symbol);
}
return value.substring(0,start) + replacedPart +value.substring(end);
}
return value;
}
}
6、根据属性上传递的字段类型处理对应的脱敏
- 注意点:
1、继承JsonSerializer类(进行序列化jsonGenerator.writeString()),实现ContextualSerializer类(beanProperty.getAnnotation())得到字段上的自定义注解的设置值,最后根据对应的类型执行对应的序列化器json操作
package com.example.demo.annotation;
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 java.io.IOException;
public class DesensitizeSerialize extends JsonSerializer<String> implements ContextualSerializer {
private JsonDesensitize jsonDesensitize;
@Override
public void serialize(String s, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
// 获取参数类型
DesensitizeType type = jsonDesensitize.type();
switch (type){
case PASSWORD : jsonGenerator.writeString(DesensitizeType.passWord(s,jsonDesensitize.symbol()));
break;
case TEL : jsonGenerator.writeString(DesensitizeType.tel(s,jsonDesensitize.symbol()));
break;
case EMAIL : jsonGenerator.writeString(DesensitizeType.email(s,jsonDesensitize.symbol()));
break;
case ID_CARD : jsonGenerator.writeString(DesensitizeType.idCard(s,jsonDesensitize.symbol()));
break;
}
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {
jsonDesensitize = beanProperty.getAnnotation(JsonDesensitize.class);
if(jsonDesensitize != null){
return this;
}
return serializerProvider.findNullKeySerializer(beanProperty.getType(),beanProperty);
}
}