手动脱敏:直接在业务逻辑层代码中对敏感数据进行逐一处理,这种方式虽然提供了较高的灵活性,但容易因人为疏忽而导致脱敏遗漏,同时也会导致代码中存在大量的重复处理逻辑,增加了维护成本。
AOP(面向切面编程):利用AOP技术,通过定义切面来拦截API接口返回的数据流,从而实现对敏感字段的统一处理。这种方法能够将脱敏逻辑从业务代码中抽离出来,实现集中管理,提高了代码的可维护性和可扩展性。然而,由于AOP的拦截机制会增加一定的处理开销,因此可能会对系统性能产生一定的影响。
自定义序列化器:在数据序列化阶段,通过集成JSON序列化框架(如Jackson)提供的自定义序列化器功能,实现对敏感字段的自动化处理。这种方法既保持了较好的性能表现,又能够将脱敏逻辑与业务逻辑完全解耦,使得代码更加清晰和易于管理。
注解+反射:通过定义自定义注解来标记那些需要进行脱敏处理的字段,然后在数据返回前,利用Java的反射机制在运行时动态地遍历这些字段并进行脱敏处理。这种方式简化了脱敏操作的使用过程,使得开发者只需通过简单的注解标记即可实现脱敏功能,同时也有利于后续对脱敏逻辑的维护和扩展。
需要使用hutool和json
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
创建自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@JacksonAnnotationsInside
@JsonSerialize(using = DesensitizeSerializer.class)
public @interface Desensitize {
DesensitizeType type() default DesensitizeType.DEFAULT;
int startInclude() default 0;
int endExclude() default 0;
}
脱敏枚举类
public enum DesensitizeType {
//默认
DEFAULT,
CUSTOM_RULE,
PHONE,
EMAIL,
ID_CARD,
BANK_CARD,
ADDRESS,
CHINESE_NAME,
PASSWORD,
}
自定义序列化类
这个序列化器的主要用途是在 JSON 序列化过程中自动对标记了 @Desensitize
注解的字段进行脱敏处理
Hutool支持的脱敏数据类型包括:
整体来说,所谓脱敏就是隐藏掉信息中的一部分关键信息,用*
代替。DesensitizedUtil类中方法,其实就是
replace方法和
hide`方法的使用,想要自定义规则进行隐藏可以仿照进行实现。
public class DesensitizeSerializer extends JsonSerializer<String> implements ContextualSerializer {
private DesensitizeType type;
private int startInclude;
private int endExclude;
public DesensitizeSerializer() {
this.type = DesensitizeType.DEFAULT;
}
public DesensitizeSerializer(DesensitizeType type) {
this.type = type;
}
//在序列化字符串时被调用,根据脱敏类型对字符串进行相应的脱敏处理。根据不同的脱敏类型,使用不同的处理方法对字符串进行脱敏,并将处理后的字符串写入JSON生成器中。
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
switch (type) {
case CUSTOM_RULE:
gen.writeString(StrUtil.hide(value, startInclude, endExclude));
break;
case PHONE:
gen.writeString(DesensitizedUtil.mobilePhone(value));
break;
case EMAIL:
gen.writeString(DesensitizedUtil.email(value));
break;
case ID_CARD:
gen.writeString(DesensitizedUtil.idCardNum(value, 1, 2));
break;
case BANK_CARD:
gen.writeString(DesensitizedUtil.bankCard(value));
break;
case ADDRESS:
gen.writeString(DesensitizedUtil.address(value, 8));
break;
case CHINESE_NAME:
gen.writeString(DesensitizedUtil.chineseName(value));
break;
case PASSWORD:
gen.writeString(DesensitizedUtil.password(value));
break;
default:
gen.writeString(value);
break;
}
}
//根据上下文信息创建自定义的序列化器,用于处理带有@Desensitize注解的属性。它通过获取注解中的脱敏类型和自定义规则的起始位置和结束位置,对实例进行相应的设置,并返回自定义的序列化器实例。
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) {
if (property != null) {
Desensitize annotation = property.getAnnotation(Desensitize.class);
if (annotation != null) {
this.type = annotation.type();
if (annotation.type() == DesensitizeType.CUSTOM_RULE) {
this.startInclude = annotation.startInclude();
this.endExclude = annotation.endExclude();
}
}
}
return this;
}
}
验证
@Data
public class UserDTO {
@Desensitize(type = DesensitizeType.CHINESE_NAME)
private String name;
@Desensitize(type = DesensitizeType.PHONE)
private String phoneNumber;
@Desensitize(type = DesensitizeType.EMAIL)
private String email;
@Desensitize(type = DesensitizeType.PASSWORD)
private String password;
@Desensitize(type = DesensitizeType.ID_CARD)
private String idCard;
@Desensitize(type = DesensitizeType.BANK_CARD)
private String bankCard;
@Desensitize(type = DesensitizeType.ADDRESS)
private String address;
@Desensitize(type = DesensitizeType.CUSTOM_RULE, startInclude = 2, endExclude = 6)
private String gameName;
}
系统可能对接多个不同的短信服务商,有时候某一个挂了需要马上切换为另一个,希望在不改动业务代码的情况下实现动态切换
它是一个对spring @Autowired
注解的扩展,能够自定义用户自己的Autowired
注入逻辑,目前实现了两个功能分别是 @SmartAutowired
和 @AutowiredProxySPI
注解,我们这里要使用的便是AutowiredProxySPI
去实现我们的动态切换逻辑。
依赖
<dependency>
<groupId>io.github.burukeyou</groupId>
<artifactId>spring-smart-di-all</artifactId>
<version>0.2.0</version>
</dependency>
在启动类上添加注解@EnableSmartDI
启动功能
编写顶层接口和各个实现类
@EnvironmentProxySPI("${sms.impl}")
public interface SmsService {
}
// 给实现类定义别名
@BeanAliasName("某腾短信服务")
@Component
public class ASmsService implements SmsService {
}
@BeanAliasName("某移短信服务")
@Component
public class BSmsService implements SmsService {
}
在配置文件中指定现在使用的服务商
sms.impl=某腾短信服务
在业务逻辑中注入后就可以具体使用
// 依赖注入
@AutowiredProxySPI
private SmsService smsService;
配置除了可以写在配置文件中,还可以写在数据库中
比如自定义DBProxySPI
注解,并标记上@ProxySPI
实现并指定具体配置获取逻辑实现类AnnotationProxyFactory
即可。
然后DBProxySPI
就可以像@EnvironmentProxySPI
一样去使用了
@Inherited
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@ProxySPI(DbProxyFactory.class) // 指定配置获取逻辑
public @interface DBProxySPI {
String value();
}
@Component
public class DbProxyFactory implements AnnotationProxyFactory<DBProxySPI> {
@Autowired
private SysConfigMapper sysConfigDao;
@Override
public Object getProxy(Class<?> targetClass,DBProxySPI spi) {
// todo 根据注解从数据库获取要注入的实现类
String configName = sysConfigDao.getConfig(spi.value());
return springContext.getBean(configName);
}
}
@DBProxySPI("${sms.impl}")
public interface SmsService {
}
先定义业务接口和不同的实现
// 接口定义
public interface PaymentService {
String process();
}
//阿里支付
@Service("aliPayService")
public class AliPayService implements PaymentService {
public String process() {
return "Alipay payment" ;
}
}
//微信支付
@Service("weixinPayService")
public class WeixinPayService implements PaymentService {
public String process() {
return "Weixin payment";
}
}
接口类
@Controller
//在配置中心的值变动后实现热更新
@RefreshScope
public class PayController {
@Value("${pay.payBeanName:aliPayService}")
private String payBeanName;
@Resource
private Map<String,PaymentService> payments = new HashMap<>();
@GetMapping("/pay")
public void pay(){
payments.get(payBeanName).process();
}
}