手动脱敏:直接在业务逻辑层代码中对敏感数据进行逐一处理,这种方式虽然提供了较高的灵活性,但容易因人为疏忽而导致脱敏遗漏,同时也会导致代码中存在大量的重复处理逻辑,增加了维护成本。
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();
    }
}