自定义注解结合策略模式实现数据脱敏
数据脱敏

常见的脱敏方式

基于成本的衡量,目前数据库层脱敏的不多,更多是前端脱敏,所以下面演示一种前端脱敏方案
前端脱敏实现
实现:后端把原始数据entity拿到后,放入vo类里面,而在 vo 类里给JavaBean字段标注注解 @Desensitive,后端返回前端之前,自动把这部分数据进行脱敏计算

代码实现
项目就是Springboot3+Mybatis-plus
pom 文件
java
<?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>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example.springbootV3</groupId>
<artifactId>springbootV3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springbootV3</name>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>3.4.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类
java
@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication{
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
配置文件
java
spring.datasource.url=jdbc:mysql://192.168.133.128:3306/wxpay?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.mapper-locations=classpath*:mapper/*.xml
controller、service、dao
java
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private DesensitiveComponent desensitiveComponent;
@GetMapping("selectById/{id}")
public ResultData<UserVo> selectById(@PathVariable("id") Integer id){
try {
User user = userService.getById(id);
UserVo userVo = new UserVo();
desensitiveComponent.desensitive(user, userVo);
return ResultData.success(userVo);
} catch (IllegalAccessException e) {
return ResultData.fail(ReturnCodeEnum.HANDLE_FAILED.getCode(), "处理失败");
}
}
}
java
@Component
public class DesensitiveComponent {
@Autowired
private List<DesensiveStrategy> desensiveStrategies;
public void desensitive(Object source, Object vo) throws IllegalAccessException {
// 将eneity属性拷贝到 vo 中
BeanUtils.copyProperties(source, vo);
// 给vo进行脱敏
Field[] fields = vo.getClass().getDeclaredFields();
// 遍历所有字段,判断是否有 @Desesitive注解
for (Field field : fields) {
if (field.isAnnotationPresent(Desensive.class)) {
field.setAccessible(true);
Object fieldValue = field.get(vo);
//拿到注解的type
Desensive annotation = field.getAnnotation(Desensive.class);
DesensiveType type = annotation.type();
// 根据不同的类型调用不同的脱敏策略
for (DesensiveStrategy stratege : desensiveStrategies) {
if (stratege.support(type)) {
String result = stratege.desensiveStrategy((String) fieldValue);
field.set(vo, result);
}
}
}
}
}
}
java
/**
* 脱敏的字段类型
*/
public enum DesensiveType {
DEFAULT,
CARD,
PHONE,
EMAIL
}
java
public interface UserService extends IService<User> {
}
java
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
java
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface Desensive {
DesensiveType type() default DesensiveType.DEFAULT;
}
java
@TableName("t_user")
@Data
public class User {
@TableId
private Integer id;
private String name;
private Integer age;
private String phone;
private String card;
private Date createTime;
}
java
@Data
public class UserVo {
private Integer id;
private String name;
private Integer age;
@Desensive(type = DesensiveType.PHONE)
private String phone;
@Desensive(type = DesensiveType.CARD)
private String card;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
}
java
public interface DesensiveStrategy {
boolean support(DesensiveType type);
String desensiveStrategy(String target);
}
java
@Component
public class PhoneDesensiveStrategy implements DesensiveStrategy{
@Override
public boolean support(DesensiveType type) {
return type.equals(DesensiveType.PHONE);
}
@Override
public String desensiveStrategy(String phone) {
if (phone.length() == 11){
return phone.substring(0, 3) + "****" + phone.substring(7, 11);
}
return "***********";
}
}
java
@Component
public class CardDesensiveStrategy implements DesensiveStrategy{
@Override
public boolean support(DesensiveType type) {
return type.equals(DesensiveType.CARD);
}
@Override
public String desensiveStrategy(String card) {
if (card.length() > 10) {
return card.substring(0, 6).concat("******");
}
return "~~~~~~~~";
}
}
最终效果
