Custom annotations to implement encryption and decryption and desensitization

Posted May 25, 20203 min read


title:ep \ _Custom annotations for encryption, decryption and desensitization

date:2020-04-28 09:44

Define custom annotations

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface PrivateData {

}

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface PrivateDataMethod {

}

First define two custom annotations, privateData and privateDataMethod, and define the @Target property as FIELD and METHOD respectively.

Construct AOP logic

  • Declaring an entry point

      @Pointcut("@ annotation(com.max.base.services.annotation.PrivateDataMethod)")
      public void annotationPointCut() {
      }

Cut in all methods with the annotation @privateDataMethod.

  • Declaration notice

    @Around("annotationPointCut()")

      public Object around(ProceedingJoinPoint joinPoint) {
          Object responseObj = null;
          try {
              Object []request = joinPoint.getArgs();
              for(Object object:request) {
                  if(object instanceof Collection) {
                      Collection collection =(Collection) object;
                      collection.forEach(var-> {
                          try {
                              handleEncrypt(var);
                          } catch(IllegalAccessException e) {
                              e.printStackTrace();
                          }
                      });
                  } else {
                      handleEncrypt(object);
                  }
              }
              responseObj = joinPoint.proceed();
              if(responseObj instanceof Collection) {
                  Collection collection =(Collection) responseObj;
                  collection.forEach(var-> {
                      try {
                          handleDecrypt(var);
                      } catch(IllegalAccessException e) {
                          e.printStackTrace();
                      }
                  });
              } else {
                  handleDecrypt(responseObj);
              }
          } catch(Throwable throwable) {
              throwable.printStackTrace();
              log.error("SecureFieldAop exception {}", throwable);
          }
          return responseObj;
      }

Aroud notification to judge the object of the method input and output, if it is a non-collection object, directly perform encryption and decryption operations, otherwise, split the collection and operate one by one

  • Handle encryption and decryption

     /**
       * Handle encryption
       * @param requestObj
       * /
      private void handleEncrypt(Object requestObj) throws IllegalAccessException {
          if(Objects.isNull(requestObj)) {
              return;
          }
          Field []fields = requestObj.getClass(). GetDeclaredFields();
          for(Field field:fields) {
              boolean hasSecureField = field.isAnnotationPresent(PrivateData.class);
              if(hasSecureField) {
                  Boolean accessible = field.isAccessible();
                  if(! accessible) {
                      field.setAccessible(true);
                  }
                  String plaintextValue =(String) field.get(requestObj);
                  String encryptValue = AseUtil.encrypt(plaintextValue, secretKey);
                  field.set(requestObj, encryptValue);
                  if(! accessible) {
                      field.setAccessible(false);
                  }
              }
          }
      }

Obtain the Field list of the object through reflection, execute the encryptValue() method for the field with the @PrivateData annotation and overwrite the original field with the encrypted string.
The decryption logic is similar to encryption, and will not be described in detail.

test

  • Identify the insert() method as a method that requires encryption

    public interface CmTenantMapper {

      int deleteByPrimaryKey(Long id);
    
      @PrivateDataMethod
      int insert(CmTenant record);
    
      int insertSelective(CmTenant record);
    
      CmTenant selectByPrimaryKey(Long id);
    
      int updateByPrimaryKeySelective(CmTenant record);
    
      int updateByPrimaryKey(CmTenant record);

    }

  • Add comments to the fields that need to be encrypted in the incoming object

    public class CmTenant {

      private Long id;
      private String tenantId;
      @PrivateData
      private String tenantName;
      private String createBy;
      private Date createDate;
      private String updateBy;
      private Date updateDate;
      private String remarks;
      private Byte delFlag;

    //set get ...

  • Call the insert method to view the data saving result

Incoming object

{
  "createBy":"Coke or Not",
  "delFlag":"NOTDELETE",
  "remarks":"Test Encryption",
  "tenantId":"996",
  "tenantName":"Produced by the chair team",
  "updateBy":"Coke or Not"
}

Database save object

  • Decryption test does not make comments, everyone try

Desensitization logic

The desensitization logic is basically the same as encryption and decryption. One thing to note is that the desensitization annotation needs to add the type type

@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Order(Ordered.HIGHEST_PRECEDENCE)
public @interface MaskingField {
    MaskingTypeEnum type();
}

Define the classification of desensitization in MaskingTypeEnum

public enum MaskingTypeEnum {
    /*identification number*/
    ID_CARD,
    /*cellphone number*/
    PHONE,
    /*address*/
    ADDRESS,
   /* Name * /
    NAME
}

Identify the field type when using MaskingTypeEnum

    @MaskingField(type = MaskingTypeEnum.NAME)
    private String cpName;

Follow-up ~~ bug ~~ function is researched by yourself, peace ~

This article is published by the blog multi-post platform OpenWrite