# spring-boot-data-security **Repository Path**: hweiyu/spring-boot-data-security ## Basic Information - **Project Name**: spring-boot-data-security - **Description**: SpringBoot实现数据脱敏响应请求 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 5 - **Forks**: 6 - **Created**: 2021-09-29 - **Last Updated**: 2023-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 介绍 * SpringBoot实现数据脱敏响应请求 * 有时,由于业务需要,敏感数据返回给第三方时,需要进行隐藏处理,但是如果一个字段一个字段的进行硬编码处理的话,不仅增加了工作量,而且后期需求变动的时候,更加是地狱般的工作量变更。下面将介绍一种高效便捷的处理方法。 * 下面,通过`身份证,姓名,密码,手机号`等等示例去演示脱敏的流程 ### 原理 * 项目使用的是SpringBoot进行搭建,所以使用内置的序列化工具`jackson`进行序列化 * 通过实现`com.fasterxml.jackson.databind.JsonSerializer`进行自定义序列化 * 通过重写`com.fasterxml.jackson.databind.ser.ContextualSerializer.createContextual`获取自定义注解的信息 ### 实现 1. 添加自定义序列化注解 * 注意,必需添加`@JacksonAnnotationsInside`和`@JsonSerialize(using = MyJsonSerializer.class)`注解,否则序列化无法生效 ```java @JacksonAnnotationsInside @JsonSerialize(using = MyJsonSerializer.class) @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface CustomSerializer { /** * 脱敏规则处理类 * @return */ Class value() default DefaultRule.class; /** * 正则,pattern和format必需同时有值。如果都有值时,优先使用正则进行规则替换 * @return */ String pattern() default ""; String format() default ""; } ``` 2. 添加自定义序列化实现类 ```java @Slf4j public class MyJsonSerializer extends JsonSerializer implements ContextualSerializer { /** * 脱敏规则 */ private BaseRule rule; @Override public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException { gen.writeString(rule.apply(value)); } @Override public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException { //获取对象属性上的自定义注解 CustomSerializer customSerializer = property.getAnnotation(CustomSerializer.class); if (null != customSerializer) { try { //根据注解的配置信息,创建对应脱敏规则处理类 this.rule = customSerializer.value().newInstance(); //如果正则信息不为空,则使用注解上的正则初始化到对应的脱敏规则处理类中 if (isNotBlank(customSerializer.pattern()) && isNotBlank(customSerializer.format())) { this.rule.setRule(new RuleItem() .setRegex(customSerializer.pattern()) .setFormat(customSerializer.format())); } return this; } catch (Exception e) { log.error("json转换处理异常", e); } } return prov.findValueSerializer(property.getType(), property); } private boolean isNotBlank(String str) { return null != str && str.trim().length() > 0; } } ``` 3. 添加脱敏规则 ```java //脱敏对象 @Data @Accessors(chain = true) public class RuleItem { /** * 正则 */ private String regex; /** * 格式化显示 */ private String format; } //脱敏处理基类 @Data public abstract class BaseRule implements Function { /** * 脱敏规则对象 */ private RuleItem rule; public String apply(String str) { if (null == str) { return null; } //初始化脱敏规则 initRule(); if (null == rule || null == rule.getRegex() || null == rule.getFormat()) { return str; } //正则替换 return str.replaceAll(rule.getRegex(), rule.getFormat()); } abstract void initRule(); } //默认脱敏处理类 public class DefaultRule extends BaseRule { @Override void initRule() { } } //身份证号脱敏处理类 public class IdCardRule extends BaseRule { /** * 仅显示前6位和后4位 */ @Override void initRule() { setRule(new RuleItem() .setRegex("(\\d{6})\\d*(\\w{4})") .setFormat("$1********$2")); } } //密码脱敏处理类 public class PasswordRule extends BaseRule { /** * 全部隐藏 */ @Override public String apply(String str) { return "******"; } @Override void initRule() { } } //手机号脱敏处理类 public class PhoneRule extends BaseRule { /** * 仅显示前3位和后4位 */ @Override void initRule() { setRule(new RuleItem() .setRegex("(\\d{3})\\d*(\\d{4})") .setFormat("$1****$2")); } } //姓名脱敏处理类 public class UserNameRule extends BaseRule { /** * 仅显示最后一个汉字 */ @Override void initRule() { setRule(new RuleItem() .setRegex("\\S*(\\S)") .setFormat("**$1")); } } ``` 4. 测试代码 ```java @RestController public class DemoController { @GetMapping(value = "/test") public Response test() { DemoRespDTO respDTO = new DemoRespDTO(); respDTO.setUserName("张三"); respDTO.setPhone("18812345678"); respDTO.setIdCard("15030319520807064X"); respDTO.setPassword("asdf12345678"); respDTO.setCustomValue("sfwegewgrergergwefwefwef"); return Response.success(respDTO); } } @NoArgsConstructor @Data @Accessors(chain = true) public class DemoRespDTO implements Serializable { private static final long serialVersionUID = 1019466745376831818L; @CustomSerializer(UserNameRule.class) private String userName; @CustomSerializer(PhoneRule.class) private String phone; @CustomSerializer(IdCardRule.class) private String idCard; @CustomSerializer(PasswordRule.class) private String password; /** * 隐藏前面10个字符 */ @CustomSerializer(pattern = "\\S{10}(\\S*)", format = "**********$1") private String customValue; } ``` 5. 结果示例 ```json { "code": 200, "message": "success", "data": { "userName": "**三", "phone": "188****5678", "idCard": "150303********064X", "password": "******", "customValue": "**********rgergwefwefwef" } } ``` * 码云:https://gitee.com/hweiyu/spring-boot-data-security