JustryDeng 4 rokov pred
rodič
commit
92fa1396ac

+ 1 - 1
class-winter-core/dependency-reduced-pom.xml

@@ -3,7 +3,7 @@
   <parent>
     <artifactId>class-winter</artifactId>
     <groupId>com.idea-aedi</groupId>
-    <version>2.0.0</version>
+    <version>2.1.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
   <artifactId>class-winter-core</artifactId>

+ 1 - 1
class-winter-core/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.idea-aedi</groupId>
         <artifactId>class-winter</artifactId>
-        <version>2.0.0</version>
+        <version>2.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

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

@@ -26,6 +26,9 @@ public class Forward {
         String originJarOrWar = parseValueByPrefixFromTail("originJarOrWar=", args);
         String includePrefix = parseValueByPrefixFromTail("includePrefix=", args);
         String excludePrefix = parseValueByPrefixFromTail("excludePrefix=", args);
+        String includeXmlPrefix = parseValueByPrefixFromTail("includeXmlPrefix=", args);
+        String excludeXmlPrefix = parseValueByPrefixFromTail("excludeXmlPrefix=", args);
+        String toClearChildElementName = parseValueByPrefixFromTail("toClearChildElementName=", args);
         String finalName = parseValueByPrefixFromTail("finalName=", args);
         String password = parseValueByPrefixFromTail("password=", args);
         String includeLibs = parseValueByPrefixFromTail("includeLibs=", args);
@@ -38,6 +41,9 @@ public class Forward {
         Logger.debug(Forward.class, "You input arg originJarOrWar -> " + originJarOrWar);
         Logger.debug(Forward.class, "You input arg includePrefix -> " + includePrefix);
         Logger.debug(Forward.class, "You input arg excludePrefix -> " + excludePrefix);
+        Logger.debug(Forward.class, "You input arg includeXmlPrefix -> " + includeXmlPrefix);
+        Logger.debug(Forward.class, "You input arg excludeXmlPrefix -> " + excludeXmlPrefix);
+        Logger.debug(Forward.class, "You input arg toClearChildElementName -> " + toClearChildElementName);
         Logger.debug(Forward.class, "You input arg finalName -> " + finalName);
         Logger.debug(Forward.class, "You input arg password -> " + password);
         Logger.debug(Forward.class, "You input arg includeLibs -> " + includeLibs);
@@ -51,6 +57,9 @@ public class Forward {
                 .originJarOrWar(originJarOrWar)
                 .includePrefix(includePrefix)
                 .excludePrefix(excludePrefix)
+                .includeXmlPrefix(includeXmlPrefix)
+                .excludeXmlPrefix(excludeXmlPrefix)
+                .toClearChildElementName(toClearChildElementName)
                 .finalName(finalName)
                 .password(password)
                 .includeLibs(includeLibs)

+ 52 - 15
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/Reverses.java

@@ -9,13 +9,19 @@ import winter.com.ideaaedi.classwinter.util.IOUtil;
 import winter.com.ideaaedi.classwinter.util.JarUtil;
 import winter.com.ideaaedi.classwinter.util.JavaagentCmdArgs;
 import winter.com.ideaaedi.classwinter.util.Logger;
+import winter.com.ideaaedi.classwinter.util.Pair;
 import winter.com.ideaaedi.classwinter.util.StrUtil;
 
 import java.io.File;
+import java.io.IOException;
 import java.lang.instrument.ClassFileTransformer;
 import java.lang.instrument.Instrumentation;
 import java.nio.charset.StandardCharsets;
 import java.security.ProtectionDomain;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.zip.ZipFile;
 
 /**
  * 反向解密(Agent类)
@@ -42,8 +48,10 @@ public class Reverses {
             Logger.ENABLE_DEBUG.set(javaagentCmdArgs.isDebug());
         }
         
-        Logger.debug(Reverses.class, "parse raw args [" + args + "] to javaagentCmdArgs -> " + javaagentCmdArgs);
-        
+        Logger.debug(Reverses.class, "Parse raw args [" + args + "] to javaagentCmdArgs -> " + javaagentCmdArgs);
+    
+        final AtomicBoolean firstExec = new AtomicBoolean(false);
+    
         // 在JVM加载class字节码之前,通过ClassFileTransformer修改字节码
         if (instrumentation != null) {
             /*
@@ -64,7 +72,6 @@ public class Reverses {
                         return classfileBuffer;
                     }
                     className = className.replace("/", ".").replace("\\", ".");
-    
                     // 本项目的印章
                     if (Cache.sealCache == null) {
                         byte[] sealByte = IOUtil.readFileFromWorkbenchRoot(new File(projectPath), Constant.SEAL_FILE);
@@ -77,23 +84,55 @@ public class Reverses {
                         Logger.debug(Reverses.class, "seal of the project is -> " + sealContent);
                         Cache.sealCache = sealContent;
                     }
-                    
+    
+                    /// ============================================ 处理non-class文件
+                    final String inputPwd = javaagentCmdArgs.getPassword();
+                    if (firstExec.compareAndSet(false, true)) {
+                        try {
+                            // 解混淆 除了class文件外的其它文件
+                            Map<String, Pair<byte[], byte[]>> resultMap =
+                                    DecryptExecutor.unMaskNonClassFiles(projectPath, inputPwd == null ? null : inputPwd.toCharArray());
+                            Map<String, byte[]> tmpMap = new HashMap<>(16);
+                            resultMap.forEach((k, v) -> {
+                                tmpMap.put(k, v.getLeft());
+                            });
+                            String finalProjectPath = projectPath;
+                            Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                                try {
+                                    Logger.debug(Reverses.class, "mask non-class files start.");
+                                    JarUtil.rewriteZipEntry(new ZipFile(finalProjectPath), tmpMap);
+                                    Logger.debug(Reverses.class, "mask non-class files end.");
+                                } catch (IOException e) {
+                                    // ignore
+                                }
+                            }));
+                        } catch (Exception e) {
+                            Logger.error(Reverses.class, ExceptionUtil.getStackTraceMessage(e));
+                            Logger.error(Reverses.class, "Decrypt non-classes fail. ");
+                            System.exit(-1);
+                            // 上一步System.exit(-1)就退出程序了,照理说是不会走到下面这里的(,不过为了以防万一,这里打出提醒, class-winter失效)
+                            for (int i = 0; i < Constant.TEN; i++) {
+                                Logger.error(Reverses.class, "!!!!!!!!!!! class-winter Invalidation. !!!!!!!!!!!");
+                            }
+                            return null;
+                        }
+                    }
+    
+                    /// ============================================ 处理class文件
                     // 判断是否应该解密
                     if (DecryptExecutor.checklistContain(projectPath, className) && DecryptExecutor.verifySeal(classfileBuffer)) {
                         //noinspection DuplicatedCode
                         try {
                             Logger.debug(Reverses.class, "Decrypt class[" + className + "] start.");
                             classfileBuffer = DecryptExecutor.process(projectPath, null, className,
-                                    javaagentCmdArgs.getPassword() == null ? null :
-                                    javaagentCmdArgs.getPassword().toCharArray());
+                                    inputPwd == null ? null :
+                                    inputPwd.toCharArray());
                             Logger.debug(Reverses.class, "Decrypt class[" + className + "] end.");
                             return classfileBuffer;
                         } catch (Exception e) {
-                            if (javaagentCmdArgs.isDebug()) {
-                                Logger.error(Reverses.class, ExceptionUtil.getStackTraceMessage(e));
-                            }
-                            Logger.error(Reverses.class, "decrypt class[" + className + "] fail. "
-                                    + (StrUtil.isEmpty(javaagentCmdArgs.getPassword()) ? e.getMessage() : "Please ensure your password is correct."));
+                            Logger.error(Reverses.class, ExceptionUtil.getStackTraceMessage(e));
+                            Logger.error(Reverses.class, "Decrypt class[" + className + "] fail. "
+                                    + (StrUtil.isEmpty(inputPwd) ? e.getMessage() : "Please ensure your password is correct."));
                             System.exit(-1);
                             // 上一步System.exit(-1)就退出程序了,照理说是不会走到下面这里的(,不过为了以防万一,这里打出提醒, class-winter失效)
                             for (int i = 0; i < Constant.TEN; i++) {
@@ -112,11 +151,9 @@ public class Reverses {
                             Logger.debug(Reverses.class, "Decrypt class[" + className + "] end.");
                             return classfileBuffer;
                         } catch (Exception e) {
-                            if (javaagentCmdArgs.isDebug()) {
-                                Logger.error(Reverses.class, ExceptionUtil.getStackTraceMessage(e));
-                            }
+                            Logger.error(Reverses.class, ExceptionUtil.getStackTraceMessage(e));
                             String lib = DecryptExecutor.parseLib(classWinterInfoDir);
-                            Logger.error(Reverses.class, "decrypt class[" + className + "] fail. \nPlease check:\n"
+                            Logger.error(Reverses.class, "Decrypt class[" + className + "] fail. \nPlease check:\n"
                                     + "\t1. Ensure 'Your lib " + lib + " need a input password ?'\n"
                                     + "\t2. Ensure 'Your lib "+ lib +"'s password is correct ?'");
                             System.exit(-1);

+ 46 - 3
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/executor/DecryptExecutor.java

@@ -6,10 +6,13 @@ import winter.com.ideaaedi.classwinter.util.Cache;
 import winter.com.ideaaedi.classwinter.util.Constant;
 import winter.com.ideaaedi.classwinter.util.EncryptUtil;
 import winter.com.ideaaedi.classwinter.util.IOUtil;
+import winter.com.ideaaedi.classwinter.util.JarUtil;
 import winter.com.ideaaedi.classwinter.util.Logger;
+import winter.com.ideaaedi.classwinter.util.Pair;
 import winter.com.ideaaedi.classwinter.util.StrUtil;
 
 import java.io.File;
+import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -17,6 +20,8 @@ import java.util.HashSet;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.zip.ZipFile;
 
 /**
  * java class解密
@@ -52,14 +57,15 @@ public class DecryptExecutor {
      * @return  解压后的字节(null-表示classLongName所代表的文件不应该被解密)
      */
     public static byte[] process(String projectPath, String classWinterInfoDir, String classLongName, char[] password) {
+        String baseWinterInfoDir = StrUtil.isBlank(classWinterInfoDir) ? Constant.DEFAULT_ENCRYPTED_BASE_SAVE_DIR : classWinterInfoDir;
         classWinterInfoDir = StrUtil.isBlank(classWinterInfoDir) ? Constant.DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR : classWinterInfoDir;
         // 读取文件
         byte[] bytes = IOUtil.readFileFromWorkbenchRoot(new File(projectPath), classWinterInfoDir + classLongName);
         // 检查密码一致性
-        boolean userIfInputPwdWhileDecrypt = checkPwdConsistency(projectPath, classWinterInfoDir, password);
+        boolean userIfInputPwdWhileDecrypt = checkPwdConsistency(projectPath, baseWinterInfoDir, password);
         // 解密
         return EncryptUtil.decrypt(bytes, userIfInputPwdWhileDecrypt ? password :
-                obtainAutoGeneratedPwdWhileEncrypt(projectPath, classWinterInfoDir));
+                obtainAutoGeneratedPwdWhileEncrypt(projectPath, baseWinterInfoDir));
     }
     
     /**
@@ -236,6 +242,43 @@ public class DecryptExecutor {
         return classWinterInfoDir.replace("_jar/", Constant.JAR_SUFFIX);
     }
     
+    /**
+     * 恢复非class文件的混淆
+     *
+     * @param zipFilePath
+     *            项目jar/war包路径
+     * @param password
+     *            项目加密密码(可能为null)
+     * @return  被替换了的ZipEntry的相对路径及重写前后的内容信息<br/>
+     *          k - ZipEntry的相对路径,如:BOOT-INF/classes/application.yml<br/>
+     *          v - 左:重写前的内容,右:重写后的内容
+     */
+    public static Map<String, Pair<byte[], byte[]>> unMaskNonClassFiles(String zipFilePath, char[] password) throws IOException {
+        final File zipFile = new File(zipFilePath);
+        byte[] nonClassFileChecklistBytes = IOUtil.readFileFromWorkbenchRoot(zipFile, Constant.ALREADY_ENCRYPTED_NON_CLASS_FILE_CHECKLIST_SAVE_FILE);
+        String nonClassFileChecklist = new String(nonClassFileChecklistBytes, StandardCharsets.UTF_8);
+        if (StrUtil.isBlank(nonClassFileChecklist)) {
+            Logger.debug(DecryptExecutor.class, "nonClassFileChecklist is empty.");
+            return new HashMap<>(1);
+        }
+        Set<String> needToDecryptNonClassFileSet = Arrays.stream(nonClassFileChecklist.split(Constant.COMMA)).collect(Collectors.toSet());
+        // 检查密码一致性
+        boolean userIfInputPwdWhileDecrypt = checkPwdConsistency(zipFilePath, Constant.DEFAULT_ENCRYPTED_BASE_SAVE_DIR, password);
+        
+        Map<String, byte[]> replaceMap = new HashMap<>(16);
+        for (String zipEntryName : needToDecryptNonClassFileSet) {
+            byte[] encryptedBytes = IOUtil.readFileFromWorkbenchRoot(zipFile, Constant.DEFAULT_ENCRYPTED_NON_CLASSES_SAVE_DIR + zipEntryName);
+            byte[] bytes = EncryptUtil.decrypt(encryptedBytes, userIfInputPwdWhileDecrypt ? password :
+                    obtainAutoGeneratedPwdWhileEncrypt(zipFilePath, Constant.DEFAULT_ENCRYPTED_BASE_SAVE_DIR));
+            // 只处理有印章的
+            if (DecryptExecutor.verifySeal(bytes)) {
+                replaceMap.put(zipEntryName, bytes);
+            }
+        }
+        return JarUtil.rewriteZipEntry(new ZipFile(zipFilePath), replaceMap);
+    }
+    
+    
     /**
      * 获取class-winter加密时自动生成的密码
      *
@@ -247,7 +290,7 @@ public class DecryptExecutor {
      */
     private static char[] obtainAutoGeneratedPwdWhileEncrypt(String projectPath, String classWinterInfoDir) {
         // 项目本身
-        if (classWinterInfoDir.equals(Constant.DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR)) {
+        if (classWinterInfoDir.equals(Constant.DEFAULT_ENCRYPTED_BASE_SAVE_DIR)) {
             if (Cache.passwordCacheForDecrypt != null) {
                 // 从缓存读取
                 return Cache.passwordCacheForDecrypt;

+ 209 - 20
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/executor/EncryptExecutor.java

@@ -1,12 +1,16 @@
 package winter.com.ideaaedi.classwinter.executor;
 
+import com.sun.org.apache.xerces.internal.impl.Constants;
 import javassist.CannotCompileException;
 import javassist.ClassPool;
 import javassist.NotFoundException;
 import org.dom4j.Document;
+import org.dom4j.DocumentException;
 import org.dom4j.Element;
 import org.dom4j.io.SAXReader;
 import org.dom4j.io.XMLWriter;
+import org.dom4j.tree.DefaultText;
+import org.xml.sax.SAXException;
 import winter.com.ideaaedi.classwinter.Reverses;
 import winter.com.ideaaedi.classwinter.author.JustryDeng;
 import winter.com.ideaaedi.classwinter.exception.ClassWinterException;
@@ -25,8 +29,12 @@ import winter.com.ideaaedi.classwinter.util.PathUtil;
 import winter.com.ideaaedi.classwinter.util.SimpleFileOrderSupport;
 import winter.com.ideaaedi.classwinter.util.StrUtil;
 
+import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.lang.reflect.UndeclaredThrowableException;
 import java.nio.charset.StandardCharsets;
 import java.security.SecureRandom;
 import java.util.ArrayList;
@@ -100,6 +108,23 @@ public class EncryptExecutor {
      */
     private final String password;
     
+    
+    /**
+     * 要加密的xml文件的zipEntry名前缀(如:)
+     */
+    private final Set<String> includeXmlPrefixSet;
+    
+    /**
+     * 要排除加密的xml文件的zipEntry名前缀
+     */
+    private final Set<String> excludeXmlPrefixSet;
+    
+    /**
+     * xml中要清除的(根节点的)子节点名称
+     */
+    private final Set<String> toClearChildElementNameSet;
+
+    
     /**
      * 要加密的类的全类名前缀(也可以精确匹配)
      */
@@ -173,8 +198,10 @@ public class EncryptExecutor {
     private final Map<String, String> libJarAndTmpDirMap = new HashMap<>(8);
     
     private EncryptExecutor(String originJarOrWar, boolean originIsJar, String finalName, String targetRootDir,
-                            String targetLibDir, String targetClassesDir, String password, Set<String> includePrefixSet,
-                            Set<String> excludePrefixSet, Set<String> includeLibSet, Set<Pair<String, String>> alreadyProtectedLibSet, String supportFile) {
+                            String targetLibDir, String targetClassesDir, String password,
+                            Set<String> includeXmlPrefixSet, Set<String> excludeXmlPrefixSet, Set<String> toClearChildElementNameSet,
+                            Set<String> includePrefixSet, Set<String> excludePrefixSet, Set<String> includeLibSet,
+                            Set<Pair<String, String>> alreadyProtectedLibSet, String supportFile) {
         this.originJarOrWar = originJarOrWar;
         this.originIsJar = originIsJar;
         this.finalName = finalName;
@@ -182,6 +209,9 @@ public class EncryptExecutor {
         this.targetLibDir = targetLibDir;
         this.targetClassesDir = targetClassesDir;
         this.password = password;
+        this.includeXmlPrefixSet = includeXmlPrefixSet;
+        this.excludeXmlPrefixSet = excludeXmlPrefixSet;
+        this.toClearChildElementNameSet = toClearChildElementNameSet;
         this.includePrefixSet = includePrefixSet;
         this.excludePrefixSet = excludePrefixSet;
         this.includeLibSet = includeLibSet;
@@ -200,60 +230,126 @@ public class EncryptExecutor {
         
         // step0. 解压jar到jarRootDir下
         List<String> filePathList = JarUtil.unJarWar(originJarOrWar, targetRootDir);
-        showProcess("step00", "step11", "un-jar-war");
-        
+        String lastStep = "step12";
+        showProcess("step00", lastStep, "un-jar-war");
+    
+        File targetRootDirFile = new File(targetRootDir);
+        if (includeXmlPrefixSet != null && includeXmlPrefixSet.size() > 0) {
+            cleanXmlFiles(filePathList, targetRootDirFile);
+        }
+        showProcess("step01", lastStep, "clean-xml-files");
+    
         // step1. jar中的某些lib可能也需要加密,这里先(将那些需要加密的lib)进行解压,(解压到对应的临时目录,以便后面进行加密处理)
         unLibJar();
-        showProcess("step01", "step11", "un-lib-jar");
+        showProcess("step02", lastStep, "un-lib-jar");
         
         // step2. 找到需要加密混淆的class文件
-        File targetRootDirFile = new File(targetRootDir);
         List<File> allFiles = IOUtil.listSubFile(targetRootDirFile, 1);
         List<File> allNeedEncryptedClassFileList = filterClasses(allFiles);
-        showProcess("step02", "step11", "filter-classes");
+        showProcess("step03", lastStep, "filter-classes");
         
         // step3.1. 根据原.class文件,生成加密后的.class文件,并存至Constant.ENCRYPTED_CLASSES_SAVE_DIR
         List<String> allAlreadyEncryptedClassFileList = encryptClasses(allNeedEncryptedClassFileList, new File(targetRootDir,
                 Constant.DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR));
         // step3.2. 生成记录已加密class的全类名的清单文件
         generateChecklistFile(allAlreadyEncryptedClassFileList);
-        showProcess("step03", "step11", "un-jar-war");
+        showProcess("step04", lastStep, "un-jar-war");
         
         // step4. 记录印章(在解密时,可通过 checklist + 印章 判断一个class是否是被class-winter加密过)
         generateSealFile();
-        showProcess("step04", "step11", "encrypt-classes");
+        showProcess("step05", lastStep, "encrypt-classes");
 
         // step5. 混淆原.class文件(即:清空方法体)
         clearClassMethod(allNeedEncryptedClassFileList);
-        showProcess("step05", "step11", "clear-class-method");
+        showProcess("step06", lastStep, "clear-class-method");
     
         // step6. 清除META-INF/maven下pom.xml中关于class-winter-plugin的相关信息
         clearWinterPluginInfo();
-        showProcess("step06", "step11", "clear-winter-plugin-info");
+        showProcess("step07", lastStep, "clear-winter-plugin-info");
         
         // step7. 添加class-winter的用于javaagent解密的代码
         addClassWinterAgent();
-        showProcess("step07", "step11", "add-class-winter-agent");
+        showProcess("step08", lastStep, "add-class-winter-agent");
         
         // step8. 汇总那些在本次加密前本身就已经是被class-winter混淆的lib的相关信息到当前项目中
         collectAlreadyProtectedLibInfo();
-        showProcess("step08", "step11", "collect-already-protected-lib-info");
+        showProcess("step09", lastStep, "collect-already-protected-lib-info");
         
         // step9. 与step1项对应,将临时目录还原为原来的lib(此时得到的lib是加密后的)
         doLibJar();
-        showProcess("step09", "step11", "do-lib-jar");
+        showProcess("step10", lastStep, "do-lib-jar");
         
         // step10. 压缩targetRootDir目录为绝对文件路径名为generatedJarWar的jar(or war)包
         String generatedJarWar = generateJarWarPath(this.originIsJar);
         JarUtil.doJarWar(targetRootDir, generatedJarWar, new SimpleFileOrderSupport(filePathList));
-        showProcess("step10", "step11", "generate-jar-war-path");
+        showProcess("step11", lastStep, "generate-jar-war-path");
         
         // step11. 删除临时目录
         IOUtil.delete(targetRootDirFile);
-        showProcess("step11", "step11", "delete-tmp-dir");
+        showProcess(lastStep, lastStep, "delete-tmp-dir");
         return generatedJarWar;
     }
     
+    /**
+     * 从给定的文件中,找到要混淆的xml文件,并进行混淆
+     *
+     * @param filePathList
+     *            文件路径集合
+     * @param targetRootDirFile
+     *            解压jar/war的根目录
+     */
+    private void cleanXmlFiles(List<String> filePathList, File targetRootDirFile) {
+        Set<String> encryptNonClassFileSet = new HashSet<>();
+        String targetRootBaseDir = targetRootDirFile.getAbsolutePath();
+        // 筛选出需要加密的non-class文件
+        Set<String> allNeedEncryptedNonClassFileSet = filePathList.stream()
+                .filter(x -> x.endsWith(Constant.XML_SUFFIX))
+                .map(filepath -> {
+                    // zipEntryName
+                    String zipEntryName = filepath.replace(targetRootBaseDir, "");
+                    zipEntryName = zipEntryName.replace('\\', '/');
+                    zipEntryName = zipEntryName.startsWith("/") ? zipEntryName.substring(1) : zipEntryName;
+                    return zipEntryName;
+                })
+                .filter(zipEntryName -> {
+                    if (excludeXmlPrefixSet != null) {
+                        boolean shouldExclude = excludeXmlPrefixSet.stream().anyMatch(zipEntryName::startsWith);
+                        if (shouldExclude) {
+                            return false;
+                        }
+                    }
+                    return includeXmlPrefixSet.stream().anyMatch(zipEntryName::startsWith);
+                })
+                .collect(Collectors.toSet());
+        
+        // 加密non-class文件
+        for (String zipEntryName : allNeedEncryptedNonClassFileSet) {
+            // 获取源数据
+            byte[] bytes = IOUtil.readFileFromWorkbenchRoot(targetRootDirFile, zipEntryName);
+            Logger.debug(EncryptExecutor.class, "Encrypt non-class[" + zipEntryName + "] start.");
+            // 加密
+            final byte[] encryptedBytes = EncryptUtil.encrypt(bytes, obtainPassword());
+            // 将加密后的数据文件存储到指定位置
+            IOUtil.toFile(encryptedBytes,
+                    new File(this.targetRootDir + File.separator + Constant.DEFAULT_ENCRYPTED_NON_CLASSES_SAVE_DIR, zipEntryName),
+                    true);
+            Logger.debug(EncryptExecutor.class, "Encrypt non-class[" + zipEntryName + "] end.");
+            encryptNonClassFileSet.add(zipEntryName);
+        }
+        
+        // 生成清单文件
+        String nonClassChecklist = String.join(Constant.COMMA, encryptNonClassFileSet);
+        IOUtil.writeContentToFile(nonClassChecklist,
+                new File(this.targetRootDir, Constant.ALREADY_ENCRYPTED_NON_CLASS_FILE_CHECKLIST_SAVE_FILE));
+        
+        // 清空原xml文件的关键节点
+        for (String zipEntryName : encryptNonClassFileSet) {
+            byte[] bytes = IOUtil.readFileFromWorkbenchRoot(targetRootDirFile, zipEntryName);
+            String cleanedXml = clearXml(new String(bytes, StandardCharsets.UTF_8), Constant.XML_PADDING_COMMENT, toClearChildElementNameSet);
+            IOUtil.writeContentToFile(cleanedXml, new File(targetRootDir, zipEntryName));
+        }
+    }
+    
     /**
      * showProcess
      *
@@ -397,7 +493,7 @@ public class EncryptExecutor {
                 return;
             } else {
                 String libChecklist = new String(checklistByte, StandardCharsets.UTF_8);
-                IOUtil.writeContentToFile(libChecklist, new File(dirAbsolutePathForLib, Constant.CHECKLIST_FILE_SIMPLE_NAME));
+                IOUtil.writeContentToFile(libChecklist, new File(dirAbsolutePathForLib, Constant.CHECKLIST_CLASS_FILE_SIMPLE_NAME));
     
                 // 所有lib的checklist汇总至checklistOfAllLibs
                 allChecklistInfoMap.put(dirRelativePathForLib, libChecklist);
@@ -733,6 +829,68 @@ public class EncryptExecutor {
                 }).collect(Collectors.toList());
     }
     
+    /**
+     * 清空xml中指定的一级子节点内容,并以指定的内容进行注释填充
+     *
+     * @param xmlContent
+     *            原xml内容
+     * @param paddingComment
+     *            填充注释内容
+     * @param toClearFirstNodeNameSet
+     *            一级子节点名称
+     * @return  清空后的xml内容
+     */
+    private String clearXml(String xmlContent, String paddingComment, Set<String> toClearFirstNodeNameSet) {
+        if (toClearFirstNodeNameSet == null || toClearFirstNodeNameSet.size() == 0) {
+            return xmlContent;
+        }
+        if (xmlContent == null || xmlContent.trim().length() == 0) {
+            return xmlContent;
+        }
+        ByteArrayOutputStream os = null;
+        try {
+            SAXReader reader = new SAXReader();
+            // 不校验xml头部
+            reader.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false);
+            Document document = reader.read(new StringReader(xmlContent));
+            Element rootElement = document.getRootElement();
+            if (rootElement == null) {
+                return xmlContent;
+            }
+            //noinspection rawtypes
+            Iterator iterator = rootElement.elementIterator();
+            if (iterator == null) {
+                return null;
+            }
+            while (iterator.hasNext()) {
+                Element element = (Element) iterator.next();
+                if (element == null) {
+                    continue;
+                }
+                if (!toClearFirstNodeNameSet.contains(element.getName())) {
+                    continue;
+                }
+                element.clearContent();
+                element.add(new DefaultText("\n\t\t"));
+                element.addComment(paddingComment + "\n\t\t");
+                element.add(new DefaultText("\n\t"));
+            }
+            os = new ByteArrayOutputStream();
+            XMLWriter xmlWriter = new XMLWriter(os);
+            xmlWriter.write(document);
+            xmlWriter.flush();
+            xmlWriter.close();
+            return new String(os.toByteArray(), StandardCharsets.UTF_8);
+        } catch (SAXException | DocumentException |IOException e) {
+            throw new UndeclaredThrowableException(e,
+                    String.format("clear xmlContent exception. \nxmlContent -> %s, \npaddingComment -> %s, \ntoClearFirstNodeNameSet -> %s",
+                            xmlContent, paddingComment, toClearFirstNodeNameSet));
+        } finally {
+            IOUtil.close(os);
+        }
+    }
+    
+    
     
     @Override
     public String toString() {
@@ -747,8 +905,11 @@ public class EncryptExecutor {
                 ", password='" + (StrUtil.isBlank(password) ? null : "******") + '\'' +
                 ", supportFile='" + supportFile + '\'' +
                 ", includePrefixSet=" + includePrefixSet +
-                ", includeLibSet=" + includeLibSet +
                 ", excludePrefixSet=" + excludePrefixSet +
+                ", includeXmlPrefixSet=" + includeXmlPrefixSet +
+                ", excludeXmlPrefixSet=" + excludeXmlPrefixSet +
+                ", toClearChildElementNameSet=" + toClearChildElementNameSet +
+                ", includeLibSet=" + includeLibSet +
                 ", alreadyProtectedLibSet=" + alreadyProtectedLibSet +
                 '}';
     }
@@ -772,6 +933,12 @@ public class EncryptExecutor {
         
         private String excludePrefix;
         
+        private String includeXmlPrefix;
+        
+        private String excludeXmlPrefix;
+        
+        private String toClearChildElementName;
+        
         private String includeLibs;
         
         private String alreadyProtectedLibs;
@@ -806,6 +973,21 @@ public class EncryptExecutor {
             this.excludePrefix = excludePrefix;
             return this;
         }
+    
+        public Builder includeXmlPrefix(String includeXmlPrefix) {
+            this.includeXmlPrefix = includeXmlPrefix;
+            return this;
+        }
+        
+        public Builder excludeXmlPrefix(String excludeXmlPrefix) {
+            this.excludeXmlPrefix = excludeXmlPrefix;
+            return this;
+        }
+        
+        public Builder toClearChildElementName(String toClearChildElementName) {
+            this.toClearChildElementName = toClearChildElementName;
+            return this;
+        }
         
         public Builder includeLibs(String includeLibs) {
             this.includeLibs = includeLibs;
@@ -838,7 +1020,8 @@ public class EncryptExecutor {
             Logger.ENABLE_DEBUG.set(this.debug != null && this.debug);
             // 2. 错误启动jar时,System.out输出的提示信息
             if (!StrUtil.isEmpty(tips)) {
-                JavassistUtil.TIPS = tips;
+                Constant.TIPS.setLength(0);
+                Constant.TIPS.append(tips);
             }
             
             // ---- 参数校验
@@ -917,8 +1100,14 @@ public class EncryptExecutor {
                 }
                 throw new ClassWinterException("includeLibs format must be shaped like xxx1.jar[,xxx2.jar,xxx3.jar]");
             }
+            
+            // xml相关参数解析
+            Set<String> includeXmlPrefixSet = StrUtil.strToSet(includeXmlPrefix);
+            Set<String> excludeXmlPrefixSet = StrUtil.strToSet(excludeXmlPrefix);
+            toClearChildElementName = StrUtil.isBlank(toClearChildElementName) ? Constant.DEFAULT_XML_NODE_NAMES : toClearChildElementName;
+            Set<String> toClearChildElementNameSet = StrUtil.strToSet(toClearChildElementName);
             return new EncryptExecutor(this.originJarOrWar, originIsJar, this.finalName, targetRootDir, targetLibDir, targetClassesDir,
-                    password, includePrefixSet, excludePrefixSet, includeLibSet, alreadyProtectedLibSet, this.supportFile);
+                    password, includeXmlPrefixSet, excludeXmlPrefixSet, toClearChildElementNameSet, includePrefixSet, excludePrefixSet, includeLibSet, alreadyProtectedLibSet, this.supportFile);
         }
     
         /**

+ 40 - 4
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/Constant.java

@@ -13,11 +13,27 @@ import java.time.format.DateTimeFormatter;
  */
 public interface Constant {
     
+    /**
+     * 被混淆了的内容的提示信息
+     */
+    StringBuffer TIPS = new StringBuffer("ERROR !!!!!!!!!!! Jar(or War) has been protected by class-winter. Please use javaagent re-start project. !!!!!!!!!!!");
+    
+    
     /**
      * 印章, 若class中存在此印章内容,也能说明该class被class-winter加密过
      */
     String SEAL = "At " + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME) + "\t" + "Random-Character " + new String(EncryptUtil.generateCharArr(32));
     
+    /**
+     * 默认需要加密的xml一级节点名称
+     */
+    String DEFAULT_XML_NODE_NAMES = "resultMap,sql,insert,update,delete,select";
+    
+    /**
+     * xml被清除后,添加的注释填充
+     */
+    String XML_PADDING_COMMENT = "\n\t\t\t" + TIPS + "\n\t\t\t" + SEAL;
+    
     /**
      * 解压jar/war包时,临时目录后缀名
      */
@@ -34,9 +50,19 @@ public interface Constant {
     String CLASSES = "classes";
     
     /**
-     * 默认的class-winter相关信息的存储目录
+     * 默认的class-winter加密存储目录base
+     */
+    String DEFAULT_ENCRYPTED_BASE_SAVE_DIR = "META-INF/winter/";
+    
+    /**
+     * 默认的class-winter加密classes相关信息的存储目录
+     */
+    String DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR = "META-INF/winter/classes";
+    
+    /**
+     * 默认的class-winter加密其它文件(如.yml .yaml .xml .properties)相关信息的存储目录
      */
-    String DEFAULT_ENCRYPTED_CLASSES_SAVE_DIR = "META-INF/winter/";
+    String DEFAULT_ENCRYPTED_NON_CLASSES_SAVE_DIR = "META-INF/winter/NON-classes";
     
     /**
      * MANIFEST.MF文件的位置
@@ -46,12 +72,17 @@ public interface Constant {
     /**
      * 记录已加密.class的全类名的清单文件
      */
-    String ALREADY_ENCRYPTED_CLASS_CHECKLIST_CLASSES_SAVE_FILE = "META-INF/winter/checklist.winter";
+    String ALREADY_ENCRYPTED_CLASS_CHECKLIST_CLASSES_SAVE_FILE = "META-INF/winter/checklist.classes.winter";
+    
+    /**
+     * 记录已加密的非class文件的条目名清单文件(内容形如: BOOT-INF/classes/application.properties,BOOT-INF/classes/application-dev.properties)
+     */
+    String ALREADY_ENCRYPTED_NON_CLASS_FILE_CHECKLIST_SAVE_FILE = "META-INF/winter/checklist.non-classes.winter";
     
     /**
      * 记录已加密.class的全类名的清单文件
      */
-    String CHECKLIST_FILE_SIMPLE_NAME = "checklist.winter";
+    String CHECKLIST_CLASS_FILE_SIMPLE_NAME = "checklist.classes.winter";
     
     /**
      * 记录本次加密印章的文件
@@ -113,6 +144,11 @@ public interface Constant {
      */
     String JAR_SUFFIX = ".jar";
     
+    /**
+     * xml文件后缀
+     */
+    String XML_SUFFIX = ".xml";
+    
     /**
      * war包后缀
      */

+ 2 - 7
class-winter-core/src/main/java/winter/com/ideaaedi/classwinter/util/JavassistUtil.java

@@ -30,12 +30,7 @@ import java.util.stream.Collectors;
  * @since 2021/4/25 20:54:30
  */
 public final class JavassistUtil {
-    
-    /**
-     * 被清空了方法体的方法内部的提示信息
-     */
-    public static String TIPS = "ERROR !!!!!!!!!!! Jar(or War) has been protected by class-winter. Please use javaagent re-start project. !!!!!!!!!!!";
-    
+
     /**
      * 清空类中的方法体,并简单处理一下main,使提示密码无效
      *
@@ -83,7 +78,7 @@ public final class JavassistUtil {
                     /// }
                     try {
                         ctMethod.insertBefore(
-                                "System.out.println(\"\\n " + TIPS +" \\n\");"
+                                "System.out.println(\"\\n " + Constant.TIPS +" \\n\");"
                                 // 也输出印章信息吧
                                 + "\nSystem.out.println(\"" + Constant.SEAL +"\");"
                                 + "\nSystem.exit(-1);"

+ 1 - 1
class-winter-maven-plugin/pom.xml

@@ -5,7 +5,7 @@
     <parent>
         <groupId>com.idea-aedi</groupId>
         <artifactId>class-winter</artifactId>
-        <version>2.0.0</version>
+        <version>2.1.0</version>
     </parent>
     <modelVersion>4.0.0</modelVersion>
 

+ 15 - 0
class-winter-maven-plugin/src/main/java/winter/com/ideaaedi/classwinter/plugin/ClassWinterPlugin.java

@@ -41,6 +41,15 @@ public class ClassWinterPlugin extends AbstractMojo {
     @Parameter
     private String excludePrefix;
     
+    @Parameter
+    private String includeXmlPrefix;
+    
+    @Parameter
+    private String excludeXmlPrefix;
+    
+    @Parameter
+    private String toClearChildElementName;
+    
     @Parameter
     private String includeLibs;
     
@@ -65,6 +74,9 @@ public class ClassWinterPlugin extends AbstractMojo {
         Logger.debug("You config arg originJarOrWar -> " + originJarOrWar);
         Logger.debug("You config arg includePrefix -> " + includePrefix);
         Logger.debug("You config arg excludePrefix -> " + excludePrefix);
+        Logger.debug("You config arg includeXmlPrefix -> " + includeXmlPrefix);
+        Logger.debug("You config arg excludeXmlPrefix -> " + excludeXmlPrefix);
+        Logger.debug("You config arg toClearChildElementName -> " + toClearChildElementName);
         Logger.debug("You config arg finalName -> " + finalName);
         Logger.debug("You config arg password -> " + password);
         Logger.debug("You config arg includeLibs -> " + includeLibs);
@@ -82,6 +94,9 @@ public class ClassWinterPlugin extends AbstractMojo {
                 .password(password)
                 .includePrefix(includePrefix)
                 .excludePrefix(excludePrefix)
+                .includeXmlPrefix(includeXmlPrefix)
+                .excludeXmlPrefix(excludeXmlPrefix)
+                .toClearChildElementName(toClearChildElementName)
                 .includeLibs(includeLibs)
                 .alreadyProtectedLibs(alreadyProtectedLibs)
                 .supportFile(supportFile)

+ 1 - 1
pom.xml

@@ -9,7 +9,7 @@
     </modules>
     <groupId>com.idea-aedi</groupId>
     <artifactId>class-winter</artifactId>
-    <version>2.0.0</version>
+    <version>2.1.0</version>
 
     <!-- 配置项目的基础信息 -->
     <name>class-winter</name>