scenario analysis
Sometimes, when displaying at the front end, sensitive information such as phone number and ID card needs to be filtered out and displayed as * * *; If only the front-end is modified, the data will still be returned, and only the back-end can be modified,
Difficult points:
1: Not all pages need to be blurred. For example, administrators and other operations cannot be blurred,
2: There are some import functions, and the exported data may also need to be blurred; Encryption cannot be performed when adding. If encryption is performed when modifying, it needs to be restored when saving;
3: Some returned field names are not uniform. Some are called phoneNum, some are called phone, and some are called managerPhone
4: Some front-end components, such as the customer drop-down box, also contain ID card information, which also needs desensitization;
5: When processing the returned result, it may be an encapsulated object, which needs traversal and recursion;
Implementation idea:
1: Permission control
The setting page adds permission control to the operator, which fields can be displayed and which fields need desensitization;
2: User defined annotations encapsulate the fuzzy types into an entity, and let the return types that need to be desensitized inherit the entity, so as to avoid adding annotations to each entity, and then carry out AOP programming to fuzzy the data;
Almost every aspect needs to be processed. Put the permission fields that need to be processed into Redis, delete and update Redis when modifying permission control;
@IgnoreEncrypt annotation. The controller marked with this annotation will not verify permissions;
If the interface passes in ignoreEncrypt=1 or uses IgnoreEncrypt to label the controller, you can make the request not verify permissions
@FieldRight annotation:
@FieldRight(fieldRightType = FieldRightType.CARD_NO) private String newcardNo;//ID number, call*
Implementation code:
1: Declare desensitized field annotations
/** * What strategy does the tag field use to desensitize */ @Documented @Inherited @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface FieldRight { FieldRightType fieldRightType() default FieldRightType.OTHER; }
The desensitization type can be customized according to the situation
package com.xqc.commoncommon.enums; public enum FieldRightType { /** * Identification Number */ CARD_NO("cardNo","Identification Number "), /** * mailbox */ EMAIL("email","mailbox"), /** * contact number */ PHONE("phone","contact number"), /** * Customer birthday */ // BIRTHDAY("birthday", "customer's birthday"), /** * Contact address */ ADDRESS("address","Contact address"), /** * Certificate address */ CARD_ADDRESS("cardAddress","Certificate address"), /** * Company Registered Address */ REGISTER_ADDRESS("registerAddress","Company Registered Address"), /** * Working address */ WORK_ADDRESS("workAddress","Working address"), /** * Portal login account */ LOGIN_NAME("loginName","Portal login account"), /** * Others (not controlled by permission) */ OTHER("other","other"), private final String field; private final String fieldName; FieldRightType(String field, String fieldName) { this.field = field; this.fieldName = fieldName; } public String getField() { return field; } public String getFieldName() { return fieldName; } }
2: Custom desensitized unified entity
3: Transform the entities that need desensitization
4: Unified processing of requests
//First, query the permissions from redis. If you can't find them, query them from the database and put them in redis //userId LoginUserInfo loginUserInfo = ServletUtil.getLoginUserInfo(request); String ignoreEncrypt = request.getParameter("ignoreEncrypt"); if (loginUserInfo != null && !"1".equals(ignoreEncrypt)) { // Get container ServletContext sc = request.getSession().getServletContext(); XmlWebApplicationContext cxt = (XmlWebApplicationContext) WebApplicationContextUtils.getWebApplicationContext(sc); // Get dispersedcachesericeimpl from container if (cxt != null && cxt.getBean("DispersedCacheSerciceImpl") != null && iDispersedCacheSercice == null) { iDispersedCacheSercice = (DispersedCacheSerciceImpl) cxt.getBean("DispersedCacheSerciceImpl"); } String key = "FieldRight:" + loginUserInfo.getUserId(); Object o = iDispersedCacheSercice.get(key); List<String> userCustomerFieldRightList = new ArrayList<>(); if (o != null) { userCustomerFieldRightList = JSONArray.parseArray(o.toString(), String.class); } else { UserCustomerFieldRightQuery query = new UserCustomerFieldRightQuery(); query.setCompanyId(loginUserInfo.getCompanyId()); query.setUserId(loginUserInfo.getUserId()); //Query the fields that need to be hidden query.setFieldRight(1); List<CommonUserCustomerFieldRightDTO> userCustomerFieldRight = iUserService.getUserCustomerFieldRight(query); //arrangement userCustomerFieldRightList = userCustomerFieldRight.stream().map(CommonUserCustomerFieldRightDTO::getField) .collect(Collectors.toList()); iDispersedCacheSercice.add(key, userCustomerFieldRightList); } //Query the user's customer permissions. If not found, it means that all permissions are released request.setAttribute("userCustomerFieldRightList",userCustomerFieldRightList); }
5: AOP processes the returned results uniformly
import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import com.xqc.annotation.FieldRight; import com.xqc.annotation.IgnoreEncrypt; import com.xqc.enums.FieldRightType; import com.xqc.utils.CommonUtil; import java.lang.reflect.Field; import java.util.List; /** * controller It is currently used for field permission control < br / > * For details on field permission control, please see {@ link FieldRight} */ @Aspect @Component @Slf4j public class CommonControllerAspect { /** * Cut into all controller s annotated with {@ link IgnoreEncrypt} */ @Pointcut("@annotation(com.xqc.annotation.IgnoreEncrypt)") public void pointcut(){} /** * The controller annotated with {@ link IgnoreEncrypt} removes the field permission check flag before entering */ @Before(value = "pointcut()") public void before(JoinPoint joinPoint) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); requestAttributes.getRequest().setAttribute("userCustomerFieldRightList",null); } /** * 1,Cut into all controller s * 2,At present (August 4, 2021), it is used for field permission verification. It is necessary to consider whether the field permission verification only filters the controller s under some package s */ @Pointcut("execution(* com.xqc.*.controller.*.*(..))") public void allControllerPointCut(){} /** * 1,After entering the controller and returning, * @param joinPoint * @param returnValue */ @AfterReturning(value = "allControllerPointCut()", returning="returnValue") @SuppressWarnings("unchecked") public void afterController(JoinPoint joinPoint,Object returnValue) { ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); List<String> fieldRightList = (List<String>)requestAttributes.getRequest().getAttribute("userCustomerFieldRightList"); try { dealFieldRight(returnValue,fieldRightList); } catch (Exception exception) { exception.printStackTrace(); } } /** * Main logic of field permission processing * 1, If the list is passed in, it will be recursive after traversal * 2, If dto is passed in, it is analyzed directly * 3, Other types are passed in. Ignore them directly */ @SuppressWarnings("unchecked") public static boolean dealFieldRight(Object model, List<String> fieldRightList) throws Exception{ //If a field needs to be verified, false is returned directly if (fieldRightList == null || fieldRightList.isEmpty()){ return false; } //If the object to be processed is empty, it will be returned directly if (model == null){ return false; } //Judge whether it is dto or list according to typeName String typeName = model.getClass().getTypeName(); //Judge whether it is a list. The typename of the list is java.util. * * * * list. Therefore, judge according to java.utll and lowercase list as keywords. If it appears at the same time, it is recognized as a list if (typeName.contains("java.util") && typeName.toLowerCase().contains("list")){ /* It's a list The loop recursively processes each element */ List modelList = (List)model; if (modelList.isEmpty()){ return false; } for (Object item : modelList) { if (item == null){ continue; } //Recursive processing, but when the first encountered element does not need to be processed, the subsequent item does not need to be processed boolean canDoNext = dealFieldRight(item,fieldRightList); if (!canDoNext){ break; } } return true; }else if (typeName.contains("com.xqc")){ //If it is not a list, judge whether it is an Object of the project according to com.xqc //The parent class needs to be read in a loop until the Object is encountered: the superClass of the Object is empty Class checkClass = model.getClass(); while (checkClass.getSuperclass()!=null){ Field[] fields = checkClass.getDeclaredFields(); for (Field field : fields) { //If it is a list, it needs to be processed recursively String type = field.getType().toString(); if (type.contains("java.util") && type.toLowerCase().contains("list")){ field.setAccessible(true); List o = (List)field.get(model); if (o == null){ continue; } if (o.isEmpty()){ continue; } for (Object item : o) { if (item == null){ continue; } boolean canDoNext = dealFieldRight(item,fieldRightList); if (!canDoNext){ break; } } } //If the field is dto, it should also be processed recursively if (type.contains("com.xqc")){ field.setAccessible(true); Object o = field.get(model); if (o != null){ dealFieldRight(model,fieldRightList); } } String fieldName; FieldRight annotation = field.getAnnotation(FieldRight.class); if (annotation != null){ if (annotation.fieldRightType() == FieldRightType.OTHER){ continue; }else{ fieldName = annotation.fieldRightType().getField(); } }else { //If there is no annotation, it will not be encrypted continue; } if (fieldRightList.contains(fieldName)) { field.setAccessible(true); Object o1 = field.get(model); if (o1 == null || "".equals(o1.toString())){ continue; } //If it is a String, it is set to an asterisk; otherwise, it is set to null if ("class java.lang.String".equals(field.getGenericType().toString())){ //Determine whether it is cardNo if (fieldName.equals(FieldRightType.CARD_NO.getField())){ //Get cardNo field.set(model, CommonUtil.cardNoSet(o1.toString())); }else{ field.set(model, "******"); } }else{ field.set(model,null); } } } checkClass = checkClass.getSuperclass(); } return true; }else { //If another type is passed in for data processing, you can ignore it directly. You only need to process the dto under list and com.xqc return false; } } }