|
|
@@ -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);
|
|
|
}
|
|
|
|
|
|
/**
|