This is a community that may be useful to you
One-to-one communication/interview brochure/resume optimization/job search questions, welcome to join the “Yudao Rapid Development Platform” Knowledge Planet. The following is some information provided by Planet:
“Project Practice (Video)”: Learn from books, “practice” from past events
“Internet High Frequency Interview Questions”: Studying with your resume, spring blossoms
“Architecture x System Design”: Overcoming difficulties and mastering high-frequency interview scenario questions
“Advancing Java Learning Guide”: systematic learning, the mainstream technology stack of the Internet
“Must-read Java Source Code Column”: Know what it is and why it is so
This is an open source project that may be useful to you
Domestic Star is a 100,000+ open source project. The front-end includes management backend + WeChat applet, and the back-end supports monomer and microservice architecture.
Functions cover RBAC permissions, SaaS multi-tenancy, data permissions, mall, payment, workflow, large-screen reports, WeChat public account, etc.:
Boot address: https://gitee.com/zhijiantianya/ruoyi-vue-pro
Cloud address: https://gitee.com/zhijiantianya/yudao-cloud
Video tutorial: https://doc.iocoder.cn
Source: juejin.cn/post/
7225848846785544247
-
1 cause
-
2 Initial attempts
-
3 Improvements to the second version
-
4 Final plan
1 Cause
Recently, I am writing a function to desensitize users’ sensitive data. After looking around on the Internet, it is basically global scope. I think it should be more flexible, and desensitization is more appropriate in different scenarios and different businesses.
For an introduction to JsonSerializer, please refer to this guy’s guide
https://juejin.cn/post/6872636051237240846
For aop introduction, please refer to this boss’s
https://juejin.cn/post/6844903575441637390
Backend management system + user applet implemented based on Spring Boot + MyBatis Plus + Vue & Element, supporting RBAC dynamic permissions, multi-tenancy, data permissions, workflow, three-party login, payment, SMS, mall and other functions
Project address: https://github.com/YunaiV/ruoyi-vue-pro
Video tutorial: https://doc.iocoder.cn/video/
2 Preliminary attempt
enum class
/** * Sensitive information enumeration class * **/ public enum PrivacyTypeEnum { /** * customize */ CUSTOMER, /** * Username, Zhang*san, Li* */ CHINESE_NAME, /** * ID number, 110110********1234 */ ID_CARD, /** * Landline number, ****1234 */ FIXED_PHONE, /** * Mobile phone number, 176****1234 */ MOBILE_PHONE, /** *Address, Beijing******** */ ADDRESS, /** * Email, s*****[email protected] */ EMAIL, /** * Bank card, 622202************1234 */ BANK_CARD, /** * Password, always ******, has nothing to do with length */ PASSWORD, /** * Key, always ******, regardless of length */ KEY }
annotation
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD})//Acts on the field @JacksonAnnotationsInside // Indicates customizing your own annotation PrivacyEncrypt @JsonSerialize(using = PrivacySerialize.class)//This annotation uses serialization public @interface PrivacyEncrypt { /** * Desensitized data type, when it is not Customer, refixNoMaskLen and suffixNoMaskLen and maskStr will be ignored */ PrivacyTypeEnum type() default PrivacyTypeEnum.CUSTOMER; /** * The length of the front does not need to be coded */ int prefixNoMaskLen() default 0; /** * The length of the post does not need to be coded */ int suffixNoMaskLen() default 0; /** * What to use for coding? */ String maskStr() default "*"; }
Serialization class
public class PrivacySerialize extends JsonSerializer<String> implements ContextualSerializer { public static final Logger logger = LoggerFactory.getLogger(PrivacySerialize.class); private PrivacyTypeEnum type; private Integer prefixNoMaskLen; private Integer suffixNoMaskLen; private String maskStr; public PrivacySerialize(PrivacyTypeEnum type, Integer prefixNoMaskLen, Integer suffixNoMaskLen, String maskStr) { this.type = type; this.prefixNoMaskLen = prefixNoMaskLen; this.suffixNoMaskLen = suffixNoMaskLen; this.maskStr = maskStr; } public PrivacySerialize() { } @Override public void serialize(String origin,JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException { if (StringUtils.isNotBlank(origin) & amp; & amp; null != type) { switch (type) { case CHINESE_NAME: jsonGenerator.writeString(DesensitizedUtils.chineseName(origin)); break; case ID_CARD: jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin)); break; case FIXED_PHONE: jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin)); break; case MOBILE_PHONE: jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin)); break; case ADDRESS: jsonGenerator.writeString(DesensitizedUtils.address(origin)); break; case EMAIL: jsonGenerator.writeString(DesensitizedUtils.email(origin)); break; case BANK_CARD: jsonGenerator.writeString(DesensitizedUtils.bankCard(origin)); break; case PASSWORD: jsonGenerator.writeString(DesensitizedUtils.password(origin)); break; case KEY: jsonGenerator.writeString(DesensitizedUtils.key(origin)); break; case CUSTOMER: jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr)); break; default: throw new IllegalArgumentException("Unknow sensitive type enum " + type); } }else { jsonGenerator.writeString(""); } } @Override public JsonSerializer<?> createContextual(SerializerProvider serializerProvider,BeanProperty beanProperty) throws JsonMappingException { if (beanProperty != null) { if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { PrivacyEncrypt encrypt = beanProperty.getAnnotation(PrivacyEncrypt.class); if (encrypt == null) { encrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class); } if (encrypt != null) { return new PrivacySerialize(encrypt.type(), encrypt.prefixNoMaskLen(), encrypt.suffixNoMaskLen(), encrypt.maskStr()); } } return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } return serializerProvider.findNullValueSerializer(null); } }
Desensitization tools
/** * Desensitization tools * **/ public class DesensitizedUtils { /** * Desensitize strings * @param origin original string * @param prefixNoMaskLen How many clear text fields need to be reserved on the left side * @param suffixNoMaskLen How many clear text fields need to be retained on the right side? * @param maskStr String used for masking, such as '*' * @return the result after desensitization */ public static String desValue(String origin, int prefixNoMaskLen, int suffixNoMaskLen, String maskStr) { if (origin == null) { return null; } StringBuilder sb = new StringBuilder(); for (int i = 0, n = origin.length(); i < n; i + + ) { if (i < prefixNoMaskLen) { sb.append(origin.charAt(i)); continue; } if (i > (n - suffixNoMaskLen - 1)) { sb.append(origin.charAt(i)); continue; } sb.append(maskStr); } return sb.toString(); } /** * [Chinese name] only displays the last Chinese character, and other characters are hidden as asterisks, such as: ** Meng * @param fullName name * @return result */ public static String chineseName(String fullName) { if (fullName == null) { return null; } return desValue(fullName, 0, 1, "*"); } /** * [ID card number] displays the first six digits and four digits, and hides the others. A total of 18 or 15 digits, for example: 340304*******1234 * @param id ID number * @return result */ public static String idCardNum(String id) { return desValue(id, 6, 4, "*"); } /** * [Landline number] The last four digits, the others are hidden, such as ****1234 * @param num landline phone * @return result */ public static String fixedPhone(String num) { return desValue(num, 0, 4, "*"); } /** * [Mobile phone number] The first three digits, the last four digits, and the others are hidden, such as 135****6810 * @param num mobile phone number * @return result */ public static String mobilePhone(String num) { return desValue(num, 3, 4, "*"); } /** * [Address] only displays the region, not the detailed address, for example: Haidian District, Beijing**** * @param address address * @return result */ public static String address(String address) { return desValue(address, 6, 0, "*"); } /** * [E-mail address: only the first letter of the email prefix is displayed, other prefixes are hidden and replaced with asterisks, @ and subsequent addresses are displayed, for example: d**@126.com * @param email email * @return result */ public static String email(String email) { return email.replaceAll("(\w?)(\w + )(\w)(@\w + \.[a-z] + (\.[a-z] + )?)", "$1****$3 $4"); } /** * [Bank card number] The first six digits, the last four digits, and other asterisks are used to hide each asterisk, such as: 622260************1234 * @param cardNum bank card number * @return result */ public static String bankCard(String cardNum) { return desValue(cardNum, 6, 4, "*"); } /** * [Password] All characters in the password are replaced with *, for example: ****** * @param password password * @return result */ public static String password(String password) { if (password == null) { return null; } return "******"; } /** * [Key] Except for the last three digits of the key, all keys are replaced with *, for example: ***xdS The length after desensitization is 6. If the length of the plaintext is less than three digits, it will be displayed according to the actual length, and the remaining positions will be filled with * * @param key key * @return result */ public static String key(String key) { if (key == null) { return null; } int viewLength = 6; StringBuilder tmpKey = new StringBuilder(desValue(key, 0, 3, "*")); if (tmpKey.length() > viewLength) { return tmpKey.substring(tmpKey.length() - viewLength); } else if (tmpKey.length() < viewLength) { int buffLength = viewLength - tmpKey.length(); for (int i = 0; i < buffLength; i + + ) { tmpKey.insert(0, "*"); } return tmpKey.toString(); } else { return tmpKey.toString(); } } }
Annotation usage
Data desensitization has indeed been achieved, but there is a problem. The current desensitization is aimed at the fact that as long as the entity class is used to return the interface, the data in it will be desensitized. In some scenarios, it is not necessary, so it is necessary. Make improvements.
Backend management system + user applet implemented based on Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element, supporting RBAC dynamic permissions, multi-tenancy, data permissions, workflow, three-party login, payment, SMS, mall and other functions
Project address: https://github.com/YunaiV/yudao-cloud
Video tutorial: https://doc.iocoder.cn/video/
3 Second Edition Improvements
My idea is to inherit a parent class in the entity class and define a field so that it can be used as a switch for desensitization, and the entity class field does not participate in the serialization desensitization control class.
public class DataMaskKey implements Serializable { //Do not serialize, set key to control filtering, not enabled by default private transient Boolean isPrivacyKey = false; public Boolean getPrivacyKey() { return isPrivacyKey; } public void setPrivacyKey(Boolean privacyKey) { isPrivacyKey = privacyKey; } }
The updated serialization class
The idea is to obtain the attributes of the member through reflection. Because we don’t know how many attributes will be inherited, we need to recursively search for the required fields.
public class PrivacySerialize extends JsonSerializer<String> implements ContextualSerializer { public static final Logger logger = LoggerFactory.getLogger(PrivacySerialize.class); private PrivacyTypeEnum type; private Integer prefixNoMaskLen; private Integer suffixNoMaskLen; private String maskStr; public PrivacySerialize(PrivacyTypeEnum type, Integer prefixNoMaskLen, Integer suffixNoMaskLen, String maskStr) { this.type = type; this.prefixNoMaskLen = prefixNoMaskLen; this.suffixNoMaskLen = suffixNoMaskLen; this.maskStr = maskStr; } public PrivacySerialize() { } @Override public void serialize(String origin,JsonGenerator jsonGenerator,SerializerProvider serializerProvider) throws IOException { boolean flag = false; //Reflection to get the object Object currentValue = jsonGenerator.getOutputContext().getCurrentValue(); //Reflection to get class Class<?> aClass = jsonGenerator.getOutputContext().getCurrentValue().getClass(); List<Field> fieldList = getFieldList(aClass); for (Field field : fieldList) { //Start reflection acquisition String name = field.getName(); if ("isPrivacyKey".equals(name)){ try { //Reassign flag = (boolean) field.get(currentValue); } catch (IllegalAccessException e) { e.printStackTrace(); } } } //Reflection for switch judgment if (flag){ //logger.info("Perform desensitization"); if (StringUtils.isNotBlank(origin) & amp; & amp; null != type) { switch (type) { case CHINESE_NAME: jsonGenerator.writeString(DesensitizedUtils.chineseName(origin)); break; case ID_CARD: jsonGenerator.writeString(DesensitizedUtils.idCardNum(origin)); break; case FIXED_PHONE: jsonGenerator.writeString(DesensitizedUtils.fixedPhone(origin)); break; case MOBILE_PHONE: jsonGenerator.writeString(DesensitizedUtils.mobilePhone(origin)); break; case ADDRESS: jsonGenerator.writeString(DesensitizedUtils.address(origin)); break; case EMAIL: jsonGenerator.writeString(DesensitizedUtils.email(origin)); break; case BANK_CARD: jsonGenerator.writeString(DesensitizedUtils.bankCard(origin)); break; case PASSWORD: jsonGenerator.writeString(DesensitizedUtils.password(origin)); break; case KEY: jsonGenerator.writeString(DesensitizedUtils.key(origin)); break; case CUSTOMER: jsonGenerator.writeString(DesensitizedUtils.desValue(origin, prefixNoMaskLen, suffixNoMaskLen, maskStr)); break; default: throw new IllegalArgumentException("Unknow sensitive type enum " + type); } }else { jsonGenerator.writeString(""); } }else { //logger.info("No desensitization"); jsonGenerator.writeString(origin); } } @Override public JsonSerializer<?> createContextual(SerializerProvider serializerProvider,BeanProperty beanProperty) throws JsonMappingException { if (beanProperty != null) { if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) { PrivacyEncrypt encrypt = beanProperty.getAnnotation(PrivacyEncrypt.class); if (encrypt == null) { encrypt = beanProperty.getContextAnnotation(PrivacyEncrypt.class); } if (encrypt != null) { return new PrivacySerialize(encrypt.type(), encrypt.prefixNoMaskLen(), encrypt.suffixNoMaskLen(), encrypt.maskStr()); } } return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty); } return serializerProvider.findNullValueSerializer(null); } private List<Field> getFieldList(Class<?> clazz){ if(null == clazz){ return null; } List<Field> fieldList = new ArrayList<>(); //Recursively search for required fields Class<?> aClass = ClassRecursionUtils.getClass(clazz, "isPrivacyKey"); Field[] declaredFields = aClass.getDeclaredFields(); for (Field field : declaredFields) { if (field != null){ //Set attribute accessibility field.setAccessible(true); //Filter static if(Modifier.isStatic(field.getModifiers())){ continue; } String name = field.getName(); //Filter non-Boolean types Class<?> type = field.getType(); //And only add isPrivacyKey if (type.isAssignableFrom(Boolean.class) & amp; & amp; "isPrivacyKey".equals(name)){ fieldList.add(field); } } } return fieldList; } }
Recursive utility class
public class ClassRecursionUtils { public static Class<?> getClass(Class<?> c, String fieldName) { if (c !=null & amp; & amp; !hasField(c, fieldName)) { return getClass(c.getSuperclass(), fieldName); } return c; } public static boolean hasField(Class<?> c, String fieldName){ Field[] fields = c.getDeclaredFields(); for (Field f : fields) { if (fieldName.equals(f.getName())) { return true; } } return false; } }
Now you only need to manually set it when encapsulating data in the entity class.
4 Final plan
In the above situation, it is possible to manually control whether to desensitize in certain scenarios, but the original code needs to be modified. I find it unfriendly, so I use aop for control.
There are basically two return types for projects
-
entity class as return
-
Pagination return
aop annotation
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD})//Acts on the method public @interface PrivacyKeyAnnotation { /** * Whether to enable serialized desensitization. It is enabled by default. */ boolean isKey() default true; /** * Is it PageInfo<?> (paging object) */ boolean isPageKey() default false; }
aop class
@Component @Aspect public class PrivacyKeyAspect { public static final Logger logger = LoggerFactory.getLogger(PrivacyKeyAspect.class); /** * @Description: Surround notification contains this annotation * @param: ProceedingJoinPoint joinPoint * @return: Object */ @Around(value = "@annotation("aop annotation address xxxxx")") public Object repeatSub(ProceedingJoinPoint joinPoint) throws Throwable { return joinPoint.proceed(); } /** * @Description: post notification */ @AfterReturning(value = "@annotation("aop annotation address")",returning = "result") public void setPrivacyKeyType(JoinPoint joinPoint, Object result) throws Throwable { //Annotate value acquisition MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); //Whether to enable desensitization boolean flag = method.getDeclaredAnnotation(PrivacyKeyAnnotation.class).isKey(); //Whether to desensitize paging boolean status = method.getDeclaredAnnotation(PrivacyKeyAnnotation.class).isPageKey(); if (!status) { //Perform return value reflection Class<?> aClass = ClassRecursionUtils.getClass(result.getClass(), "isPrivacyKey"); if (null != aClass) { setFieldMethod(result, flag, aClass); } } else { //Reflection paging page //Reflection list type Parameter[] parameters = signature.getMethod().getParameters(); //generic name String name = parameters[0].getName(); //generic class Class<?> type = parameters[0].getType(); //Package names String typeName = type.getName(); PropertyDescriptor[] ps = Introspector.getBeanInfo(result.getClass(), Object.class).getPropertyDescriptors(); for (PropertyDescriptor prop : ps) { if (prop.getPropertyType().isAssignableFrom(List.class)) { //List collection type Object obj = result.getClass().getMethod(prop.getReadMethod().getName()).invoke(result); if(obj !=null){ List<?> listObj = (List<?>) obj; for (Object next : listObj) { Class<?> classObj = Class.forName(typeName); //Get member variables Class<?> keyClass = ClassRecursionUtils.getClass(classObj, "isPrivacyKey"); setFieldMethod(next, flag, keyClass); } } } } } } /** * Content filling */ private void setFieldMethod(Object result, boolean flag, Class<?> aClass) throws IllegalAccessException { Field[] declaredFields = aClass.getDeclaredFields(); for (Field field : declaredFields) { //Set attribute accessibility field.setAccessible(true); //Only get isPrivacyKey String name = field.getName(); //Filter non-Boolean types Class<?> type = field.getType(); //And only add isPrivacyKey if (type.isAssignableFrom(Boolean.class) & amp; & amp; "isPrivacyKey".equals(name)){ //Rewrite field.set(result,flag); } } } }
Use write on service implement class method
Finally, for another implementation method, you can refer to:
https://juejin.cn/post/7242145254057410615
Welcome to join my knowledge planet and comprehensively improve your technical capabilities.
To join, “Long press” or “Scan” the QR code below:
Planet’s content includes: project practice, interviews and recruitment, source code analysis, and learning routes.
If the article is helpful, please read it and forward it. Thank you for your support (*^__^*)
The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java Skill TreeHomepageOverview 139041 people are learning the system