SpringBoot 实战:轻松实现接口数据脱敏
各位 Java 摸鱼搭子们,今天又偷偷划水了没?我是天天摸鱼的 Java 工程师,虽然摸鱼快乐,但代码也不能落下!这不,今天就抽空给大家整点硬核干货 ——SpringBoot 里接口数据脱敏的实战技巧,手把手教你给敏感数据套上 “隐形衣”,摸鱼之余也能轻松搞定工作难题,快搬好小板凳来听课
有没有遇到过这种情况:开发完接口,测试跑通了,开开心心准备上线,突然被安全部门拦住 ——“接口返回的用户手机号、身份证号这些敏感信息得做脱敏处理,不然分分钟泄露隐私!” 别慌,今天就手把手教你在 SpringBoot 项目里,用注解轻松实现接口数据脱敏,从此和数据泄露风险说拜拜!
为啥非得做数据脱敏?
先唠唠为啥要做数据脱敏。举个例子,咱们做个用户管理系统,用户登录后获取个人信息的接口要是直接返回完整的手机号 “13800138000”、身份证号 “11010519491231002X”,万一接口被非法调用,这些隐私信息就全暴露了。做了脱敏后,返回 “138****8000”“110105********002X”,既不影响正常业务使用,又保护了用户隐私,一举两得!
用注解实现脱敏的原理是啥?
可能有小伙伴好奇,注解到底是怎么实现数据脱敏的?简单来说,注解就像是给代码贴的 “小标签”,告诉程序哪些数据需要特殊处理。我们自定义一个Desensitize注解,标注在需要脱敏的字段上。然后利用 Spring 的 AOP(面向切面编程),在接口返回数据之前,拦截这些标注了注解的字段,调用脱敏方法进行处理,最后再把脱敏后的数据返回给调用方。
动手开干:一步步实现数据脱敏
1. 创建 SpringBoot 项目
首先,咱们得有个 SpringBoot 项目。最简单的办法就是通过 Spring Initializr(start.spring.io/)创建。进去后,选好项目的 Group、Artifact,依赖里勾选spring-web(因为咱们要开发接口),点击 “Generate” 下载项目压缩包,解压后用 IDEA 打开,项目就搭好啦!
2. 定义脱敏注解
在项目里新建一个Desensitize注解类,用来标记需要脱敏的字段。代码如下:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表示这个注解在运行时还能被读取和使用
@Retention(RetentionPolicy.RUNTIME)
// 说明这个注解只能用于类的字段上
@Target({ElementType.FIELD})
public @interface Desensitize {
// 定义脱敏类型,比如手机号、身份证号等
DesensitizeType type();
}
这里还用到了一个DesensitizeType枚举类,用来定义不同的脱敏类型,代码如下:
public enum DesensitizeType {
PHONE, // 手机号
ID_CARD, // 身份证号
BANK_CARD // 银行卡号
// 可以根据需要添加更多类型
}
3. 编写脱敏处理器
光有注解不行,还得有个 “干活的” 来执行脱敏操作。创建一个DesensitizeProcessor类,专门负责根据不同的脱敏类型进行数据处理,代码如下:
import org.springframework.stereotype.Component;
@Component
public class DesensitizeProcessor {
// 根据传入的值和脱敏类型,进行数据脱敏
public String desensitize(String value, DesensitizeType type) {
if (value == null) {
return null;
}
// 根据不同的脱敏类型,执行不同的脱敏逻辑
switch (type) {
case PHONE:
// 把手机号中间四位替换成****,正则表达式匹配手机号中间四位
return value.replaceAll("(\d{3})\d{4}(\d{4})", "$1****$2");
case ID_CARD:
// 把身份证号中间八位替换成********,正则表达式匹配身份证号中间八位
return value.replaceAll("(\d{6})\d{8}(\d{4})", "$1********$2");
case BANK_CARD:
// 把银行卡号中间12位替换成************,正则表达式匹配银行卡号中间12位
return value.replaceAll("(\d{4})\d{12}(\d{4})", "$1************$2");
default:
return value;
}
}
}
4. 使用 AOP 切面实现自动脱敏
有了注解和处理器,接下来用 AOP 切面来实现自动脱敏。创建一个DesensitizeAspect切面类,代码如下:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.util.Objects;
@Aspect
@Component
public class DesensitizeAspect {
@Autowired
private DesensitizeProcessor desensitizeProcessor;
// 定义切面的切点,当方法上有@ResponseBody或@ApiResponse注解时触发
@Around("@annotation(org.springframework.web.bind.annotation.ResponseBody) || @annotation(io.swagger.annotations.ApiResponse)")
public Object desensitize(ProceedingJoinPoint joinPoint) throws Throwable {
// 先执行目标方法,获取返回结果
Object result = joinPoint.proceed();
if (result != null) {
// 对返回结果进行脱敏处理
handleObject(result);
}
return result;
}
private void handleObject(Object obj) {
if (obj == null) {
return;
}
Class<?> clazz = obj.getClass();
// 获取类的所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 获取字段上的Desensitize注解
Desensitize desensitize = field.getAnnotation(Desensitize.class);
if (desensitize != null) {
// 设置字段可访问(因为有些字段是private的)
field.setAccessible(true);
try {
// 获取字段的值
Object value = field.get(obj);
if (value instanceof String) {
// 如果值是字符串类型,进行脱敏处理
String desensitizedValue = desensitizeProcessor.desensitize((String) value, desensitize.type());
// 把脱敏后的值重新设置回字段
field.set(obj, desensitizedValue);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
// 如果字段类型是复杂对象(不是基本类型、String和枚举),递归处理
if (isComplexObject(field.getType())) {
handleObject(ReflectionUtils.getField(field, obj));
}
}
}
private boolean isComplexObject(Class<?> clazz) {
return!clazz.isPrimitive() &&!clazz.equals(String.class) &&!clazz.isEnum() && clazz.isClass();
}
}
这个切面类的作用就是在接口返回数据前,检查返回对象里带有Desensitize注解的字段,调用DesensitizeProcessor进行脱敏。如果返回对象里还有其他对象,也会递归处理,确保所有敏感数据都能被脱敏。
5. 在接口中使用注解
最后一步,在接口返回对象里,给需要脱敏的字段加上Desensitize注解。比如有个用户信息返回类:
public class UserInfo {
private String name;
@Desensitize(type = DesensitizeType.PHONE)
private String phone;
@Desensitize(type = DesensitizeType.ID_CARD)
private String idCard;
// 省略getter和setter方法
}
这样,当接口返回UserInfo对象时,phone和idCard字段就会自动被脱敏啦!
总结
通过自定义注解、脱敏处理器和 AOP 切面这一套组合拳,咱们在 SpringBoot 项目里实现了接口数据的自动脱敏。以后再遇到数据脱敏需求,直接给字段贴个注解,剩下的交给程序自动处理。而且代码结构清晰,后期维护也方便。赶紧把这个技能用到项目里,让你的接口更安全吧!要是在实践过程中遇到问题,欢迎来交流,咱们一起把代码写得更漂亮!