改造了以前寫的數據脫敏插件,更好用了
以前用Mybatis插件的形式寫了一個數據脫敏工具,但是發現有一定的局限性。很多時候我們從ORM查詢到的數據有其它邏輯要處理,比如根據電話號查詢用戶信息,你脫敏了就沒有辦法來處理該邏輯了。所以脫敏這個步驟需要后置,放在JSON序列化這個階段比較合適。今天就來實現這個功能。
Jackson序列化中脫敏
改造過程其實就是把脫敏后置到JSON序列化過程中,這里我使用Jackson類庫。
原來Mybatis插件中的脫敏注解是這樣的:
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface Sensitive {
- SensitiveStrategy strategy();
- }
脫敏的策略是這樣的:
- import java.util.function.Function;
- /**
- * 脫敏策略.
- *
- * @author felord.cn
- * @since 11 :25
- */
- public enum SensitiveStrategy {
- /**
- * Username sensitive strategy.
- */
- USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")),
- /**
- * Id card sensitive type.
- */
- ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\w{4})", "$1****$2")),
- /**
- * Phone sensitive type.
- */
- PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")),
- /**
- * Address sensitive type.
- */
- ADDRESS(s -> s.replaceAll("(\\S{3})\\S{2}(\\S*)\\S{2}", "$1****$2****"));
- private final Function<String, String> desensitizer;
- SensitiveStrategy(Function<String, String> desensitizer) {
- this.desensitizer = desensitizer;
- }
- public Function<String, String> desensitizer() {
- return desensitizer;
- }
- }
我將改造它們,是它們在JSON序列化中實現字段屬性脫敏。
自定義脫敏序列化
這里我們首先實現自定義的脫敏序列化邏輯:
- 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;
- import java.util.Objects;
- /**
- * @author felord.cn
- * @since 1.0.8.RELEASE
- */
- public class SensitiveJsonSerializer extends JsonSerializer<String> implements ContextualSerializer {
- private SensitiveStrategy strategy;
- @Override
- public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
- gen.writeString(strategy.desensitizer().apply(value));
- }
- @Override
- public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
- Sensitive annotation = property.getAnnotation(Sensitive.class);
- if (Objects.nonNull(annotation)&&Objects.equals(String.class, property.getType().getRawClass())) {
- this.strategy = annotation.strategy();
- return this;
- }
- return prov.findValueSerializer(property.getType(), property);
- }
- }
其中createContextual方法用來獲取實體類上的@Sensitive注解并根據條件初始化對應的JsonSerializer對象;而顧名思義,serialize方法執行脫敏序列化邏輯。
改造脫敏注解
然后就是改造@Sensitive,把上面自定義的JSON序列化和脫敏策略綁定到一起。這里用到了Jackson的捆綁注解@JacksonAnnotationsInside,它的作用是將多個注解組合到一起;另外一個是序列化注解@JsonSerialize,它的作用是聲明使用我上面自定義的序列化方法。
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- @JacksonAnnotationsInside
- @JsonSerialize(using = SensitiveJsonSerializer.class)
- public @interface Sensitive {
- SensitiveStrategy strategy();
- }
使用
這就改造完成了,我們來試一試。我們定義一個需要脫敏的實體類并根據字段標記上對應的脫敏注解:
- /**
- * @author felord.cn
- * @since 1.0.8.RELEASE
- */
- @Data
- public class User {
- /**
- * 真實姓名
- */
- @Sensitive(strategy = SensitiveStrategy.USERNAME)
- private String realName;
- /**
- * 地址
- */
- @Sensitive(strategy = SensitiveStrategy.ADDRESS)
- private String address;
- /**
- * 電話號碼
- */
- @Sensitive(strategy = SensitiveStrategy.PHONE)
- private String phoneNumber;
- /**
- * 身份證號碼
- */
- @Sensitive(strategy = SensitiveStrategy.ID_CARD)
- private String idCard;
- }
然后Jackson來序列化User實例:
- User user = new User();
- user.setRealName("張三豐");
- user.setPhoneNumber("13333333333");
- user.setAddress("湖北省十堰市丹江口市武當山");
- user.setIdCard("4333333333334334333");
- ObjectMapper objectMapper = new ObjectMapper();
- String json = objectMapper.writeValueAsString(user);
- System.out.println(json);
可以得到:
- {
- "realName":"張*豐",
- "address":"湖北省****市丹江口市武****",
- "phoneNumber":"133****3333",
- "idCard":"4333****34333"
- }
效果還是可以的,當然如果能加個開關就更好了,根據不同的場景來決定是否進行脫敏。
本文轉載自微信公眾號「碼農小胖哥」,可以通過以下二維碼關注。轉載本文請聯系碼農小胖哥公眾號。