JustryDeng 3 лет назад
Родитель
Сommit
59d287d407

+ 4 - 4
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/Forward.java

@@ -23,10 +23,10 @@ public class Forward {
      */
     public static void main(String[] args) {
         /// main test
-        ///args =new String[]{
-        ///        "originJarOrWar=C:/Users/Administrator/Desktop/test.war",
-        ///        "includePrefix=com.ideaaedi.test"
-        ///};
+        args =new String[]{
+                "originJarOrWar=D:\\working\\demo\\target\\demo-1.0.0.jar",
+                "includePrefix=com.ideaaedi"
+        };
         // 解析参数
         String originJarOrWar = parseValueByPrefixFromTail("originJarOrWar=", args);
         String includePrefix = parseValueByPrefixFromTail("includePrefix=", args);

+ 79 - 27
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/executor/EncryptExecutor.java

@@ -16,6 +16,7 @@ import winter.com.ideaaedi.classwinter.author.JustryDeng;
 import winter.com.ideaaedi.classwinter.exception.ClassWinterException;
 import winter.com.ideaaedi.classwinter.util.Cache;
 import winter.com.ideaaedi.classwinter.util.Constant;
+import winter.com.ideaaedi.classwinter.util.EncryptClassArgs;
 import winter.com.ideaaedi.classwinter.util.EncryptUtil;
 import winter.com.ideaaedi.classwinter.util.ExceptionUtil;
 import winter.com.ideaaedi.classwinter.util.FileOrderSupport;
@@ -45,6 +46,7 @@ import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -81,6 +83,7 @@ public class EncryptExecutor {
         CLASS_WINTER_FILES.add(PathUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(SimpleFileOrderSupport.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(StrUtil.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
+        CLASS_WINTER_FILES.add(EncryptClassArgs.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(Reverses.class.getName().replace(".", "/") + Constant.CLASS_SUFFIX);
         CLASS_WINTER_FILES.add(Reverses.class.getName().replace(".", "/").replace("Reverses", "Reverses$1") + Constant.CLASS_SUFFIX);
     }
@@ -125,6 +128,8 @@ public class EncryptExecutor {
     
     /**
      * 要加密的类的全类名前缀(也可以精确匹配)
+     * <br />
+     * 注:可携带参数, 写法同url后面待参数 url?key1=value1&key2=value2
      */
     private final Set<String> includePrefixSet;
     
@@ -164,7 +169,7 @@ public class EncryptExecutor {
      *  在一些外置lib的项目中,可能需要用到此参数;如果是内置lib,忽略此参数即可
      * </pre>
      */
-    private String alreadyProtectedRootDir;
+    private final String alreadyProtectedRootDir;
     
     /**
      * 已经被class-winter加密过了的lib的信息
@@ -264,12 +269,12 @@ public class EncryptExecutor {
         
         // step3. 找到需要加密混淆的class文件
         List<File> allFiles = IOUtil.listSubFile(targetRootDirFile, 1);
-        List<File> allNeedEncryptedClassFileList = filterClasses(allFiles);
+        List<EncryptClassArgs> allNeedEncryptedClassInfoList = filterClasses(allFiles);
         showProcess("step03", lastStep, "filter-classes");
         
         // step4.1. 根据原.class文件,生成加密后的.class文件,并存至Constant.ENCRYPTED_CLASSES_SAVE_DIR
-        List<String> allAlreadyEncryptedClassFileList = encryptClasses(allNeedEncryptedClassFileList, new File(targetRootDir,
-                Constant.DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR));
+        List<String> allAlreadyEncryptedClassFileList = encryptClasses(allNeedEncryptedClassInfoList,
+                new File(targetRootDir, Constant.DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR));
         // step4.2. 生成记录已加密class的全类名的清单文件
         generateChecklistFile(allAlreadyEncryptedClassFileList);
         showProcess("step04", lastStep, "encrypt-classes");
@@ -283,7 +288,7 @@ public class EncryptExecutor {
         showProcess("step06", lastStep, "setting-jvm-arg-check-items");
 
         // step7. 混淆原.class文件(即:清空方法体)
-        clearClassMethod(allNeedEncryptedClassFileList);
+        clearClassMethod(allNeedEncryptedClassInfoList);
         showProcess("step07", lastStep, "clear-class-method");
     
         // step8. 清除META-INF/maven下pom.xml中关于class-winter-plugin的相关信息
@@ -368,6 +373,9 @@ public class EncryptExecutor {
         // 清空原xml文件的关键节点
         for (String zipEntryName : encryptNonClassFileSet) {
             byte[] bytes = IOUtil.readFileFromWorkbenchRoot(targetRootDirFile, zipEntryName);
+            if (bytes == null) {
+                continue;
+            }
             String cleanedXml = clearXml(new String(bytes, StandardCharsets.UTF_8), "\n\t\t\t" + Constant.TIPS + "\n\t\t\t" + Constant.SEAL, toCleanXmlChildElementNameSet);
             IOUtil.writeContentToFile(cleanedXml, new File(targetRootDir, zipEntryName));
         }
@@ -398,9 +406,7 @@ public class EncryptExecutor {
      *
      * <?xml version="1.0" encoding="UTF-8"?>
      * <project ...>
-     *
      *     ......
-     *
      *     <build>
      *         <plugins>
      *         	// 要把这部分清除 start
@@ -757,10 +763,9 @@ public class EncryptExecutor {
     /**
      * 清空class文件的方法体,并保留参数信息
      *
-     * @param classFiles
-     *            需要清空方法体的class文件
+     * @param allNeedEncryptedClassInfoList 需要加密的class相关参数集合
      */
-    private void clearClassMethod(List<File> classFiles) {
+    private void clearClassMethod(List<EncryptClassArgs> allNeedEncryptedClassInfoList) {
         // step0. 初始化javassist
         ClassPool pool = ClassPool.getDefault();
         // step1. 把所有可能涉及到的jar、.class添加进加载路径
@@ -774,10 +779,11 @@ public class EncryptExecutor {
             throw new ClassWinterException(e);
         }
         // step2. 修改class方法体,并保存文件
-        classFiles.forEach(classFile -> {
+        allNeedEncryptedClassInfoList.forEach(encryptClassArgs -> {
+            File classFile = encryptClassArgs.getEncryptClassFile();
             String className = JavassistUtil.resolveClassName(classFile.getAbsolutePath(), true);
             try {
-                byte[] bts = JavassistUtil.clearMethodBody(pool, className, true);
+                byte[] bts = JavassistUtil.clearMethodBody(pool, className, encryptClassArgs);
                 IOUtil.toFile(bts, classFile, true);
             } catch (NotFoundException e) {
                 // ignore
@@ -865,13 +871,12 @@ public class EncryptExecutor {
     /**
      * 加密class文件,并将加密后的class文件放在savaDir里
      *
-     * @param classFiles
-     *            需要加密的class文件
-     * @param savaDir
-     *            加密后的class文件的存储目录
+     * @param encryptClassInfoList 需要加密的class相关参数集合
+     * @param savaDir 加密后的class文件的存储目录
      * @return  已经加密了的类的全类名(如: com.aaa.bbb.Abc)
      */
-    private List<String> encryptClasses(List<File> classFiles, File savaDir) {
+    private List<String> encryptClasses(List<EncryptClassArgs> encryptClassInfoList, File savaDir) {
+        List<File> classFiles = encryptClassInfoList.stream().map(EncryptClassArgs::getEncryptClassFile).collect(Collectors.toList());
         // 保证目录存在
         JarUtil.guarantyDirExist(savaDir);
         classFiles.stream().map(File::getName).forEach(name -> {
@@ -924,35 +929,82 @@ public class EncryptExecutor {
     /**
      * 找出所有需要加密的class文件
      *
-     * @param allFileList
-     *            所有文件
-     * @return  需要加密的class文件
+     * @param allFileList 所有文件
+     * @return  需要加密的class信息
      */
-    private List<File> filterClasses(List<File> allFileList) {
+    private List<EncryptClassArgs> filterClasses(List<File> allFileList) {
         return allFileList.stream()
                 .filter(file -> file.getName().endsWith(Constant.CLASS_SUFFIX))
-                .filter(file -> {
+                .map(file -> {
                     // 全类名
                     String classLongName = JavassistUtil.resolveClassName(file.getAbsolutePath(), true);
                     if (excludePrefixSet != null) {
                         for (String excludePrefix : excludePrefixSet) {
                             if (classLongName.startsWith(excludePrefix)) {
                                 // 排除
-                                return false;
+                                return null;
                             }
                         }
         
                     }
                     if (includePrefixSet != null) {
                         for (String includePrefix : includePrefixSet) {
-                            if (classLongName.startsWith(includePrefix)) {
+                            Pair<String, Map<String, String>> pair = extraPrefixAndParam(includePrefix);
+                            String pureIncludePrefix = pair.getLeft();
+                            Map<String, String> params = pair.getRight();
+                            
+                            if (classLongName.startsWith(pureIncludePrefix)) {
                                 // 包含
-                                return true;
+                                return EncryptClassArgs.create(file,
+                                        StrUtil.isTrueDefault(params.get(Constant.CLEAN_CLASS_ANNOTATION_PARAM_NAME), false),
+                                        StrUtil.isTrueDefault(params.get(Constant.CLEAN_METHOD_ANNOTATION_PARAM_NAME), false),
+                                        StrUtil.isTrueDefault(params.get(Constant.CLEAN_FIELD_ANNOTATION_PARAM_NAME), false),
+                                        StrUtil.isTrueDefault(params.get(Constant.KEEP_ORIGIN_ARGS_NAME_PARAM_NAME), true)
+                                );
                             }
                         }
                     }
-                    return false;
-                }).collect(Collectors.toList());
+                    return null;
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toList());
+    }
+    
+    /**
+     * 从includePrefix中抽取纯净的前缀及相关参数
+     * <br />
+     * 注:参考从url中获取参数
+     *
+     * @param includePrefix 要加密的类前缀
+     *                      <p>
+     *                      可携带参数,如:com.ideaaedi.demo.service?key1=value1&key2=value2
+     *
+     * @return <ul>
+     *     <li>左-要加密的类前缀</li>
+     *     <li>右-相关加密参数信息</li>
+     * </ul>
+     */
+    private Pair<String, Map<String, String>> extraPrefixAndParam(String includePrefix) {
+        Map<String, String> param = new HashMap<>(8);
+        int index = includePrefix.indexOf("?");
+        if (index < 0) {
+           return Pair.of(includePrefix, param);
+        }
+        String pureIncludePrefix = includePrefix.substring(0, index);
+        String paramStr = includePrefix.substring(index + 1);
+        for (String keyValue : paramStr.split("&")) {
+            int equalIndex = keyValue.indexOf("=");
+            if (equalIndex < 0) {
+                continue;
+            }
+            String key = keyValue.substring(0, equalIndex).trim();
+            String value = keyValue.substring(0, equalIndex).trim();
+            if (StrUtil.isBlank(key) || StrUtil.isBlank(value)) {
+                continue;
+            }
+            param.put(key, value);
+        }
+        return Pair.of(pureIncludePrefix, param);
     }
     
     /**

+ 28 - 0
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/Constant.java

@@ -234,4 +234,32 @@ public interface Constant {
      * NotFoundException标识
      */
     String NOT_FOUND_EXCEPTION_FLAG = "javassist.NotFoundException:";
+    
+    /**
+     * 参数名:是否清除类上的注解. 值为boolean类型
+     * <p>
+     * {@link EncryptClassArgs#isCleanAnnotationOverClazz()}
+     */
+    String CLEAN_CLASS_ANNOTATION_PARAM_NAME = "cca";
+    
+    /**
+     * 参数名:是否清除方法的注解. 值应为boolean类型
+     * <p>
+     * {@link EncryptClassArgs#isCleanAnnotationOverMethod()}
+     */
+    String CLEAN_METHOD_ANNOTATION_PARAM_NAME = "cma";
+    
+    /**
+     * 参数名:是否清除字段的注解. 值应为boolean类型
+     * <p>
+     * {@link EncryptClassArgs#isCleanAnnotationOverField()}
+     */
+    String CLEAN_FIELD_ANNOTATION_PARAM_NAME = "cfa";
+    
+    /**
+     * 参数名:是否保留原方法的参数名. 值应为boolean类型
+     * <p>
+     * {@link EncryptClassArgs#isKeepOriginArgsName()}
+     */
+    String KEEP_ORIGIN_ARGS_NAME_PARAM_NAME = "koa";
 }

+ 106 - 0
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/EncryptClassArgs.java

@@ -0,0 +1,106 @@
+package winter.com.ideaaedi.classwinter.util;
+
+import java.io.File;
+import java.util.Objects;
+
+/**
+ * 加密类相关参数
+ *
+ * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
+ * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
+ * @since 2.8.0
+ */
+public class EncryptClassArgs {
+    
+    /**
+     * 待加密的class文件
+     */
+    private File encryptClassFile;
+    
+    /**
+     * 是否清除类上的注解
+     */
+    private boolean cleanAnnotationOverClazz = false;
+    
+    /**
+     * 是否清除方法的注解
+     */
+    private boolean cleanAnnotationOverMethod = false;
+    
+    /**
+     * 是否清除字段的注解
+     */
+    private boolean cleanAnnotationOverField = false;
+    
+    /**
+     * 是否保留原方法的参数名
+     */
+    private boolean keepOriginArgsName = true;
+    
+    /**
+     * fast create
+     */
+    public static EncryptClassArgs create(File encryptClassFile, boolean cleanAnnotationOverClazz,
+                                          boolean cleanAnnotationOverMethod, boolean cleanAnnotationOverField,
+                                          boolean keepOriginArgsName) {
+        Objects.requireNonNull(encryptClassFile, "encryptClassFile cannot be null.");
+        EncryptClassArgs encryptClassArgs = new EncryptClassArgs();
+        encryptClassArgs.setEncryptClassFile(encryptClassFile);
+        encryptClassArgs.setCleanAnnotationOverClazz(cleanAnnotationOverClazz);
+        encryptClassArgs.setCleanAnnotationOverMethod(cleanAnnotationOverMethod);
+        encryptClassArgs.setCleanAnnotationOverField(cleanAnnotationOverField);
+        encryptClassArgs.setKeepOriginArgsName(keepOriginArgsName);
+        return encryptClassArgs;
+    }
+    
+    public File getEncryptClassFile() {
+        return encryptClassFile;
+    }
+    
+    public void setEncryptClassFile(File encryptClassFile) {
+        this.encryptClassFile = encryptClassFile;
+    }
+    
+    public boolean isCleanAnnotationOverClazz() {
+        return cleanAnnotationOverClazz;
+    }
+    
+    public void setCleanAnnotationOverClazz(boolean cleanAnnotationOverClazz) {
+        this.cleanAnnotationOverClazz = cleanAnnotationOverClazz;
+    }
+    
+    public boolean isCleanAnnotationOverMethod() {
+        return cleanAnnotationOverMethod;
+    }
+    
+    public void setCleanAnnotationOverMethod(boolean cleanAnnotationOverMethod) {
+        this.cleanAnnotationOverMethod = cleanAnnotationOverMethod;
+    }
+    
+    public boolean isCleanAnnotationOverField() {
+        return cleanAnnotationOverField;
+    }
+    
+    public void setCleanAnnotationOverField(boolean cleanAnnotationOverField) {
+        this.cleanAnnotationOverField = cleanAnnotationOverField;
+    }
+    
+    public boolean isKeepOriginArgsName() {
+        return keepOriginArgsName;
+    }
+    
+    public void setKeepOriginArgsName(boolean keepOriginArgsName) {
+        this.keepOriginArgsName = keepOriginArgsName;
+    }
+    
+    @Override
+    public String toString() {
+        return "EncryptClassArgs{" +
+                "encryptClassFile=" + encryptClassFile +
+                ", cleanAnnotationOverClazz=" + cleanAnnotationOverClazz +
+                ", cleanAnnotationOverMethod=" + cleanAnnotationOverMethod +
+                ", cleanAnnotationOverField=" + cleanAnnotationOverField +
+                ", keepOriginArgsName=" + keepOriginArgsName +
+                '}';
+    }
+}

+ 73 - 16
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/JavassistUtil.java

@@ -3,8 +3,11 @@ package winter.com.ideaaedi.classwinter.util;
 import javassist.CannotCompileException;
 import javassist.ClassPool;
 import javassist.CtClass;
+import javassist.CtField;
 import javassist.CtMethod;
 import javassist.NotFoundException;
+import javassist.bytecode.AnnotationsAttribute;
+import javassist.bytecode.AttributeInfo;
 import javassist.bytecode.BadBytecode;
 import javassist.bytecode.Bytecode;
 import javassist.bytecode.CodeAttribute;
@@ -32,12 +35,9 @@ public final class JavassistUtil {
     /**
      * 清空类中的方法体,并简单处理一下main,使提示密码无效
      *
-     * @param classPool
-     *            javassist的classPool
-     * @param className
-     *            要修改的class类的全类名
-     * @param keepOriginArgsName
-     *            是否保留原方法的参数名
+     * @param classPool javassist的classPool
+     * @param className 要修改的class类的全类名
+     * @param encryptClassArgs 加密相关参数
      *
      * @return  处理后的类的字节
      * @throws NotFoundException 当清空的类中涉及到了某些其他的类,但是根本就没有引入(这些其他的类)相应的依赖时,就会抛出此异常(即:这个类本身就报错来着)
@@ -46,7 +46,8 @@ public final class JavassistUtil {
      *                           注:在某些其它情况下,也会抛出此异常。
      * @throws  CannotCompileException 统一封装异常
      */
-    public static byte[] clearMethodBody(ClassPool classPool, String className, boolean keepOriginArgsName) throws CannotCompileException, NotFoundException {
+    public static byte[] clearMethodBody(ClassPool classPool, String className, EncryptClassArgs encryptClassArgs)
+            throws CannotCompileException, NotFoundException {
         CtClass ctClass;
         try {
             ctClass = classPool.getCtClass(className);
@@ -54,6 +55,19 @@ public final class JavassistUtil {
                 Logger.debug(JavassistUtil.class, "defrost class " + className);
                 ctClass.defrost();
             }
+            // 清空注解
+            boolean cleanOverCLass = encryptClassArgs.isCleanAnnotationOverClazz();
+            boolean cleanOverMethod = encryptClassArgs.isCleanAnnotationOverMethod();
+            boolean cleanOverField = encryptClassArgs.isCleanAnnotationOverField();
+            if (cleanOverCLass || cleanOverMethod || cleanOverField) {
+                try {
+                    clearAnnotation(ctClass, cleanOverCLass, cleanOverMethod, cleanOverField);
+                } catch (Exception ignore) {
+                    Logger.warn(JavassistUtil.class, "clean className [" + className + "] annotation fail. ignore.");
+                    // ignore
+                }
+            }
+            boolean keepOriginArgsName = encryptClassArgs.isKeepOriginArgsName();
             CtMethod[] methods = ctClass.getDeclaredMethods();
             for (CtMethod ctMethod : methods) {
                 // 当前类中的方法 && 非构造方法
@@ -67,18 +81,12 @@ public final class JavassistUtil {
                     } else {
                         ctMethod.setBody(null);
                     }
-                    /// 顺带处理一下main方法:提示jar包已经被保护,请使用javaagent启动项目
-                    /// if (isMain(ctMethod)) {
-                    ///     ctMethod.insertBefore(
-                    ///             "System.out.println(\"\\n " + TIPS +" \\n\");"
-                    ///                     + "\nSystem.exit(-1);"
-                    ///     );
-                    /// }
                     try {
                         ctMethod.insertBefore(
-                                "System.out.println(\"\\n " + Constant.TIPS +" \\n\");"
+                                "System.out.println(\"\\n " + Constant.TIPS + " \\n\");"
                                 // 也输出印章信息吧
-                                + "\nSystem.out.println(\" " + Constant.SEAL +" \");"
+                                + "\nSystem.out.println(\" Curr class '" + className + "' \");"
+                                + "\nSystem.out.println(\" " + Constant.SEAL + " \");"
                                 + "\nSystem.exit(-1);"
                         );
                     } catch (CannotCompileException e) {
@@ -97,6 +105,55 @@ public final class JavassistUtil {
         }
     }
     
+    /**
+     * 清空注解
+     *
+     * @param ctClass 目标类
+     * @param cleanOverCLass 是否清空类上的注解
+     * @param cleanOverMethod 是否清空方法上的注解
+     * @param cleanOverField 是否清空字段上的注解
+     */
+    private static void clearAnnotation(CtClass ctClass, boolean cleanOverCLass, boolean cleanOverMethod, boolean cleanOverField) {
+        // 清空类上的注解
+        if (cleanOverCLass) {
+            removeAnnotationsAttribute(ctClass.getClassFile().getAttributes());
+        }
+        // 清空方法上的注解
+        if (cleanOverMethod) {
+            CtMethod[] methods = ctClass.getDeclaredMethods();
+            for (CtMethod ctMethod : methods) {
+                removeAnnotationsAttribute(ctMethod.getMethodInfo().getAttributes());
+            }
+        }
+        // 清空字段上的注解
+        if (cleanOverField) {
+            CtField[] fields = ctClass.getDeclaredFields();
+            for (CtField declaredField : fields) {
+                removeAnnotationsAttribute(declaredField.getFieldInfo().getAttributes());
+            }
+        }
+    }
+    
+    /**
+     * 移除注解属性
+     *
+     * @param attributeInfoList 属性信息集合
+     * @return 操作是否成功
+     */
+    @SuppressWarnings("UnusedReturnValue")
+    private static boolean removeAnnotationsAttribute(List<AttributeInfo> attributeInfoList) {
+        if (attributeInfoList == null || attributeInfoList.size() == 0) {
+            return true;
+        }
+        return attributeInfoList.removeIf(x -> {
+            try {
+                return x instanceof AnnotationsAttribute;
+            } catch (Exception e) {
+                return false;
+            }
+        });
+    }
+    
     /**
      * 加载paths指定的.jar文件(或加载对应子孙目录下的所有jar包)
      *

+ 14 - 0
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/StrUtil.java

@@ -28,6 +28,20 @@ public final class StrUtil {
         return str == null || str.length() == 0;
     }
     
+    /**
+     * 判断字符串是否为true
+     *
+     * @param str 字符串
+     * @param defaultValue 字符串为空时的默认值
+     * @return  是否为true
+     */
+    public static boolean isTrueDefault(String str, boolean defaultValue) {
+        if (isBlank(str)) {
+            return defaultValue;
+        }
+        return str.equalsIgnoreCase("true");
+    }
+    
     /**
      * <p>Checks if a CharSequence is empty (""), null or whitespace only.</p>
      *