SpringBoot项目中新增脱敏功能
项目背景
目前正在开发一个SpringBoot项目,此项目有Web端和微信小程序端。web端提供给工作人员使用,微信小程序提供给群众进行预约操作。项目中有部分敏感数据需要脱敏传递给微信小程序,给与群众查看。
项目需求描述
项目中,由于使用端有两个,对于两个端的数据权限并不一样。Web端可以查看所有数据,小程序端只能查看脱敏后的数据。
需要开发一个通用脱敏功能
- 手动进行脱敏操作
- 支持多种对象,
- 支持不同字段,并脱敏指定字段
- 字段的脱敏方式多样
- 字段的脱敏方式可自定义
项目解决方案
1. 解决方案
使用注解方式
,来支持对指定字段,不同字段,多种脱敏操作,并可以脱离对象。
使用工具对象,通过泛型传参,来支持对不同对象的脱敏操作。
2. 实现代码
2.1 注解 Sensitive
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- /**
- * 自定义数据脱敏
- *
- * 例如: 身份证,手机号等信息进行模糊处理
- *
- * @author lzddddd
- */
- @Retention(RetentionPolicy.RUNTIME)
- @Target(ElementType.FIELD)
- public @interface Sensitive {
-
- /**
- * 脱敏数据类型
- */
- SensitiveType type() default SensitiveType.CUSTOMER;
-
- /**
- * 前置不需要打码的长度
- */
- int prefixNoMaskLen() default 0;
-
- /**
- * 后置不需要打码的长度
- */
- int suffixNoMaskLen() default 0;
-
- /**
- * 用什么打码
- */
- String symbol() default "*";
-
- }
2.1 脱敏类型枚举 SensitiveType
- public enum SensitiveType {
-
- /**
- * 自定义
- */
- CUSTOMER,
- /**
- * 名称
- **/
- CHINESE_NAME,
- /**
- * 身份证证件号
- **/
- ID_CARD_NUM,
- /**
- * 手机号
- **/
- MOBILE_PHONE,
- /**
- * 固定电话
- */
- FIXED_PHONE,
- /**
- * 密码
- **/
- PASSWORD,
- /**
- * 银行卡号
- */
- BANKCARD,
- /**
- * 邮箱
- */
- EMAIL,
- /**
- * 地址
- */
- ADDRESS,
-
- }
2.3 脱敏工具 DesensitizedUtils
- import com.ruoyi.common.annotation.Sensitive;
- import com.ruoyi.common.constant.HttpStatus;
- import com.ruoyi.common.core.domain.AjaxResult;
- import com.ruoyi.common.enums.SensitiveType;
- import lombok.extern.slf4j.Slf4j;
-
- import java.lang.reflect.Field;
- import java.util.*;
-
- @Slf4j
- public class DesensitizedUtils<T> {
-
- /**
- * 脱敏数据列表
- */
- private List<T> list;
-
- /**
- * 注解列表
- */
- private List<Object[]> fields;
-
- /**
- * 实体对象
- */
- public Class<T> clazz;
-
-
- public DesensitizedUtils(Class<T> clazz)
- {
- this.clazz = clazz;
- }
-
- /**
- * 初始化数据
- *
- * @param list 需要处理数据
- */
- public void init(List<T> list){
- if (list == null)
- {
- list = new ArrayList<T>();
- }
- this.list = list;
-
- // 得到所有定义字段
- createSensitiveField();
- }
-
- /**
- * 初始化数据
- *
- * @param t 需要处理数据
- */
- public void init(T t){
-
- list = new ArrayList<T>();
-
- if (t != null)
- {
- list.add(t);
- }
-
- // 得到所有定义字段
- createSensitiveField();
- }
-
- /**
- * 得到所有定义字段
- */
- private void createSensitiveField()
- {
- this.fields = new ArrayList<Object[]>();
- List<Field> tempFields = new ArrayList<>();
- tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
- tempFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
- for (Field field : tempFields)
- {
- // 单注解
- if (field.isAnnotationPresent(Sensitive.class))
- {
- putToField(field, field.getAnnotation(Sensitive.class));
- }
-
- // 多注解
- // if (field.isAnnotationPresent(Excels.class))
- // {
- // Excels attrs = field.getAnnotation(Excels.class);
- // Excel[] excels = attrs.value();
- // for (Excel excel : excels)
- // {
- // putToField(field, excel);
- // }
- // }
- }
- }
-
- /**
- * 对list数据源将其里面的数据进行脱敏处理
- *
- * @param list
- * @return 结果
- */
- public AjaxResult desensitizedList(List<T> list){
-
- if (list == null){
- return AjaxResult.error("脱敏数据为空");
- }
-
- // 初始化数据
- this.init(list);
-
- int failTimes = 0;
-
- for (T t: this.list) {
- if ((Integer)desensitization(t).get("code") != HttpStatus.SUCCESS){
- failTimes++;
- }
- }
-
- if (failTimes >0){
- return AjaxResult.error("脱敏操作中出现失败",failTimes);
- }
-
- return AjaxResult.success();
- }
-
- /**
- * 放到字段集合中
- */
- private void putToField(Field field, Sensitive attr)
- {
- if (attr != null)
- {
- this.fields.add(new Object[] { field, attr });
- }
- }
-
- /**
- * 脱敏:JavaBean模式脱敏
- *
- * @param t 需要脱敏的对象
- * @return
- */
- public AjaxResult desensitization(T t) {
- if (t == null){
- return AjaxResult.error("脱敏数据为空");
- }
-
- // 初始化数据
- init(t);
-
- try {
- // 遍历处理需要进行 脱敏的字段
- for (Object[] os : fields)
- {
- Field field = (Field) os[0];
- Sensitive sensitive = (Sensitive) os[1];
- // 设置实体类私有属性可访问
- field.setAccessible(true);
- desensitizeField(sensitive,t,field);
- }
- return AjaxResult.success(t);
- } catch (Exception e) {
- e.printStackTrace();
- log.error("日志脱敏处理失败,回滚,详细信息:[{}]", e);
- return AjaxResult.error("脱敏处理失败",e);
- }
- }
-
- /**
- * 对类的属性进行脱敏
- *
- * @param attr 脱敏参数
- * @param vo 脱敏对象
- * @param field 脱敏属性
- * @return
- */
- private void desensitizeField(Sensitive attr, T vo, Field field) throws IllegalAccessException {
-
- if (attr == null || vo == null || field == null){
- return ;
- }
-
- // 读取对象中的属性
- Object value = field.get(vo);
- SensitiveType sensitiveType = attr.type();
- int prefixNoMaskLen = attr.prefixNoMaskLen();
- int suffixNoMaskLen = attr.suffixNoMaskLen();
- String symbol = attr.symbol();
-
- //获取属性后现在默认处理的是String类型,其他类型数据可扩展
- Object val = convertByType(sensitiveType, value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- field.set(vo, val);
- }
-
- /**
- * 以类的属性的get方法方法形式获取值
- *
- * @param o 对象
- * @param name 属性名
- * @return value
- * @throws Exception
- */
- private Object getValue(Object o, String name) throws Exception
- {
- if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name))
- {
- Class<?> clazz = o.getClass();
- Field field = clazz.getDeclaredField(name);
- field.setAccessible(true);
- o = field.get(o);
- }
- return o;
- }
-
- /**
- * 根据不同注解类型处理不同字段
- */
- private Object convertByType(SensitiveType sensitiveType, Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
- switch (sensitiveType) {
- case CUSTOMER:
- value = customer(value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- break;
- case CHINESE_NAME:
- value = chineseName(value, symbol);
- break;
- case ID_CARD_NUM:
- value = idCardNum(value, symbol);
- break;
- case MOBILE_PHONE:
- value = mobilePhone(value, symbol);
- break;
- case FIXED_PHONE:
- value = fixedPhone(value, symbol);
- break;
- case PASSWORD:
- value = password(value, symbol);
- break;
- case BANKCARD:
- value = bankCard(value, symbol);
- break;
- case EMAIL:
- value = email(value, symbol);
- break;
- case ADDRESS:
- value = address(value, symbol);
- break;
- }
- return value;
- }
-
- /*--------------------------下面的脱敏工具类也可以单独对某一个字段进行使用-------------------------*/
-
- /**
- * 【自定义】 根据设置进行配置
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public Object customer(Object value, int prefixNoMaskLen, int suffixNoMaskLen, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
-
- return value;
- }
-
- /**
- * 对字符串进行脱敏处理
- *
- * @param s 需处理数据
- * @param prefixNoMaskLen 开头展示字符长度
- * @param suffixNoMaskLen 结尾展示字符长度
- * @param symbol 填充字符
- * @return
- */
- private String handleString(String s, int prefixNoMaskLen, int suffixNoMaskLen, String symbol){
- // 是否为空
- if (StringUtils.isBlank(s)) {
- return "";
- }
-
- // 如果设置为空之类使用 * 代替
- if (StringUtils.isBlank(symbol)){
- symbol = "*";
- }
-
- // 对长度进行判断
- int length = s.length();
- if (length > prefixNoMaskLen + suffixNoMaskLen){
- String namePrefix = StringUtils.left(s, prefixNoMaskLen);
- String nameSuffix = StringUtils.right(s, suffixNoMaskLen);
- s = StringUtils.rightPad(namePrefix, StringUtils.length(s) - suffixNoMaskLen, symbol).concat(nameSuffix);
- }
-
- return s;
- }
-
- /**
- * 【中文姓名】只显示第一个汉字,其他隐藏为2个星号,比如:李**
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String chineseName(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示一个字符
- int prefixNoMaskLen = 1;
- int suffixNoMaskLen = 0;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
- }
-
- /**
- * 【身份证号】显示最后四位,其他隐藏。共计18位或者15位,比如:*************1234
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String idCardNum(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 结尾只展示四个字符
- int prefixNoMaskLen = 0;
- int suffixNoMaskLen = 4;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
- }
-
- /**
- * 【固定电话】 显示后四位,其他隐藏,比如:*******3241
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String fixedPhone(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 结尾只展示四个字符
- int prefixNoMaskLen = 0;
- int suffixNoMaskLen = 4;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
-
- }
-
- /**
- * 【手机号码】前三位,后四位,其他隐藏,比如:135****6810
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String mobilePhone(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示三个字符 结尾只展示四个字符
- int prefixNoMaskLen = 3;
- int suffixNoMaskLen = 4;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
- }
-
- /**
- * 【地址】只显示到地区,不显示详细地址,比如:湖南省长沙市岳麓区***
- * 只能处理 省市区的数据
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return
- */
- public String address(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示九个字符
- int prefixNoMaskLen = 9;
- int suffixNoMaskLen = 0;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
- }
-
- /**
- * 【电子邮箱】 邮箱前缀仅显示第一个字母,前缀其他隐藏,用星号代替,@及后面的地址显示,比如:d**@126.com
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String email(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示一个字符 结尾只展示@及后面的地址
- int prefixNoMaskLen = 1;
- int suffixNoMaskLen = 4;
-
- String s = (String) value;
- if (StringUtils.isBlank(s)) {
- return "";
- }
-
- // 获取最后一个@
- int lastIndex = StringUtils.lastIndexOf(s, "@");
- if (lastIndex <= 1) {
- return s;
- } else {
- suffixNoMaskLen = s.length() - lastIndex;
- }
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
-
- }
-
- /**
- * 【银行卡号】前六位,后四位,其他用星号隐藏每位1个星号,比如:6222600**********1234
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return 脱敏后数据
- */
- public String bankCard(Object value, String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
- int prefixNoMaskLen = 6;
- int suffixNoMaskLen = 4;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
-
- }
-
- /**
- * 【密码】密码的全部字符都用*代替,比如:******
- *
- * @param value 需处理数据
- * @param symbol 填充字符
- * @return
- */
- public String password(Object value,String symbol) {
-
- //针对字符串的处理
- if (value instanceof String){
- // 对前后长度进行设置 默认 开头只展示六个字符 结尾只展示四个字符
- int prefixNoMaskLen = 0;
- int suffixNoMaskLen = 0;
-
- return handleString((String) value, prefixNoMaskLen, suffixNoMaskLen, symbol);
- }
- return "";
- }
- }
3 使用实例
3.1 需注解对象
- public class User {
- private static final long serialVersionUID = 1L;
-
- /** 普通用户ID */
- private Long userId;
-
- /** 昵称 */
- @Sensitive(type = SensitiveType.CUSTOMER,prefixNoMaskLen = 2,suffixNoMaskLen = 1)
- private String nickName;
-
- /** 姓名 */
- @Sensitive(type = SensitiveType.CHINESE_NAME)
- private String userName;
-
- /** 身份证 */
- @Sensitive(type = SensitiveType.ID_CARD_NUM)
- private String identityCard;
-
- /** 手机号码 */
- @Sensitive(type = SensitiveType.MOBILE_PHONE)
- private String phoneNumber;
- }
3.2 脱敏操作
- // 脱敏对象
- User user = new User();
- ......
- DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
- desensitizedUtils.desensitization(user);
-
- //脱敏队列
- List<User> users = new ArrayList<>();
- ......
- DesensitizedUtils<User> desensitizedUtils = new DesensitizedUtils<>(User.class);
- desensitizedUtils.desensitizedList(users);
到此这篇关于SpringBoot项目中新增脱敏功能的文章就介绍到这了,更多相关SpringBoot脱敏内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!