欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot詳細(xì)講解通過(guò)自定義classloader加密保護(hù)class文件

 更新時(shí)間:2022年04月25日 11:52:52   作者:code2roc  
這篇文章主要介紹了SpringBoot通過(guò)自定義classloader加密class文件,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

背景

最近針對(duì)公司框架進(jìn)行關(guān)鍵業(yè)務(wù)代碼進(jìn)行加密處理,防止通過(guò)jd-gui等反編譯工具能夠輕松還原工程代碼,相關(guān)混淆方案配置使用比較復(fù)雜且針對(duì)springboot項(xiàng)目問題較多,所以針對(duì)class文件加密再通過(guò)自定義的classloder進(jìn)行解密加載,此方案并不是絕對(duì)安全,只是加大反編譯的困難程度,防君子不防小人,整體加密保護(hù)流程圖如下圖所示

maven插件加密

使用自定義maven插件對(duì)編譯后指定的class文件進(jìn)行加密,加密后的class文件拷貝到指定路徑,這里是保存到resource/coreclass下,刪除源class文件,加密使用的是簡(jiǎn)單的DES對(duì)稱加密

 @Parameter(name = "protectClassNames", defaultValue = "")
 private List<String> protectClassNames;
 @Parameter(name = "noCompileClassNames", defaultValue = "")
 private List<String> noCompileClassNames;
 private List<String> protectClassNameList = new ArrayList<>();
 private void protectCore(File root) throws IOException {
        if (root.isDirectory()) {
            for (File file : root.listFiles()) {
                protectCore(file);
            }
        }
        String className = root.getName().replace(".class", "");
        if (root.getName().endsWith(".class")) {
            //class篩選
            boolean flag = false;
            if (protectClassNames!=null && protectClassNames.size()>0) {
                for (String item : protectClassNames) {
                    if (className.equals(item)) {
                        flag = true;
                    }
                }
            }
            if(noCompileClassNames.contains(className)){
                boolean deleteResult = root.delete();
                if(!deleteResult){
                    System.gc();
                    deleteResult = root.delete();
                }
                System.out.println("【noCompile-deleteResult】:" + deleteResult);
            }
            if (flag && !protectClassNameList.contains(className)) {
                protectClassNameList.add(className);
                System.out.println("【protectCore】:" + className);
                FileOutputStream fos = null;
                try {
                    final byte[] instrumentBytes = doProtectCore(root);
                    //加密后的class文件保存路徑
                    String folderPath = output.getAbsolutePath() + "\\" + "classes";
                    File  folder = new File(folderPath);
                    if(!folder.exists()){
                        folder.mkdir();
                    }
                    folderPath = output.getAbsolutePath() + "\\" + "classes"+ "\\" + "coreclass" ;
                    folder = new File(folderPath);
                    if(!folder.exists()){
                        folder.mkdir();
                    }
                    String filePath = output.getAbsolutePath() + "\\" + "classes" + "\\" + "coreclass" + "\\" + className + ".class";
                    System.out.println("【filePath】:" + filePath);
                    File protectFile = new File(filePath);
                    if (protectFile.exists()) {
                        protectFile.delete();
                    }
                    protectFile.createNewFile();
                    fos = new FileOutputStream(protectFile);
                    fos.write(instrumentBytes);
                    fos.flush();
                } catch (MojoExecutionException e) {
                    System.out.println("【protectCore-exception】:" + className);
                    e.printStackTrace();
                } finally {
                    if (fos != null) {
                        fos.close();
                    }
                    if(root.exists()){
                        boolean deleteResult = root.delete();
                        if(!deleteResult){
                            System.gc();
                            deleteResult = root.delete();
                        }
                        System.out.println("【protectCore-deleteResult】:" + deleteResult);
                    }
                }
            }
        }
    }
    private byte[] doProtectCore(File clsFile) throws MojoExecutionException {
        try {
            FileInputStream inputStream = new FileInputStream(clsFile);
            byte[] content = ProtectUtil.encrypt(inputStream);
            inputStream.close();
            return content;
        } catch (Exception e) {
            throw new MojoExecutionException("doProtectCore error", e);
        }
    }

注意事項(xiàng)

1.加密后的文件也是class文件,為了防止在遞歸查找中重復(fù)加密,需要對(duì)已經(jīng)加密后的class名稱記錄防止重復(fù)

2.在刪除源文件時(shí)可能出現(xiàn)編譯占用的情況,執(zhí)行System.gc()后方可刪除

3.針對(duì)自定義插件的列表形式的configuration節(jié)點(diǎn)可以使用List來(lái)映射

插件使用配置如圖所示

自定義classloader

創(chuàng)建CustomClassLoader繼承自ClassLoader,重寫findClass方法只處理裝載加密后的class文件,其他class交有默認(rèn)加載器處理,需要注意的是默認(rèn)處理不能調(diào)用super.finclass方法,在idea調(diào)試沒問題,打成jar包運(yùn)行就會(huì)報(bào)加密的class中的依賴class無(wú)法加載(ClassNoDefException/ClassNotFoundException),這里使用的是當(dāng)前線程的上下文的類加載器就沒有問題(Thread.currentThread().getContextClassLoader())

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> clz = findLoadedClass(name);
        //先查詢有沒有加載過(guò)這個(gè)類。如果已經(jīng)加載,則直接返回加載好的類。如果沒有,則加載新的類。
        if (clz != null) {
            return clz;
        }
        String[] classNameList = name.split("\\.");
        String classFileName = classNameList[classNameList.length - 1];
        if (classFileName.endsWith("MethodAccess") || !classFileName.endsWith("CoreUtil")) {
            return Thread.currentThread().getContextClassLoader().loadClass(name);
        }
        ClassLoader parent = this.getParent();
        try {
            //委派給父類加載
            clz = parent.loadClass(name);
        } catch (Exception e) {
            //log.warn("parent load class fail:"+ e.getMessage(),e);
        }
        if (clz != null) {
            return clz;
        } else {
            byte[] classData = null;
            ClassPathResource classPathResource = new ClassPathResource("coreclass/" + classFileName + ".class");
            InputStream is = null;
            try {
                is = classPathResource.getInputStream();
                classData = DESEncryptUtil.decryptFromByteV2(FileUtil.convertStreamToByte(is), "xxxxxxx");
            } catch (Exception e) {
                e.printStackTrace();
                throw new ProtectClassLoadException("getClassData error");
            } finally {
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (classData == null) {
                throw new ClassNotFoundException();
            } else {
                clz = defineClass(name, classData, 0, classData.length);
            }
            return clz;
        }
    }
}

隱藏classloader

classloader加密class文件處理方案的漏洞在于自定義類加載器是完全暴露的,只需進(jìn)行分析解密流程就能獲取到原始class文件,所以我們需要對(duì)classloder的內(nèi)容進(jìn)行隱藏

1.把classloader的源文件在編譯期間進(jìn)行刪除(maven自定義插件實(shí)現(xiàn))

2.將classloder的內(nèi)容進(jìn)行base64編碼后拆分內(nèi)容尋找多個(gè)系統(tǒng)啟動(dòng)注入點(diǎn)寫入到loader.key文件中(拆分時(shí)寫入的路徑和文件名需要進(jìn)行base64加密避免全局搜索),例如

    private static void init() {
        String source = "dCA9IG5hbWUuc3BsaXQoIlxcLiIpOwogICAgICAgIFN0cmluZyBjbGFzc0ZpbGVOYW1lID0gY2xhc3NOYW1lTGlzdFtjbGFzc05hbWVMaXN0Lmxlbmd0aCAtIDFdOwogICAgICAgIGlmIChjbGFzc0ZpbGVOYW1lLmVuZHNXaXRoKCJNZXRob2RBY2Nlc3MiKSB8fCAhY2xhc3NGaWxlTmFtZS5lbmRzV2l0aCgiQ29yZVV0aWwiKSkgewogICAgICAgICAgICByZXR1cm4gVGhyZWFkLmN1cnJlbnRUaHJlYWQoKS5nZXRDb250ZXh0Q2xhc3NMb2FkZXIoKS5sb2FkQ2xhc3MobmFtZSk7CiAgICAgICAgfQogICAgICAgIENsYXNzTG9hZGVyIHBhcmVudCA9IHRoaXMuZ2V0UGFyZW50KCk7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgLy/lp5TmtL7nu5nniLbnsbvliqDovb0KICAgICAgICAgICAgY2x6ID0gcGFyZW50LmxvYWRDbGFzcyhuYW1lKTsKICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAvL2xvZy53YXJuKCJwYXJlbnQgbG9hZCBjbGFzcyBmYWls77yaIisgZS5nZXRNZXNzYWdlKCksZSk7CiAgICAgICAgfQogICAgICAgIGlmIChjbHogIT0gbnVsbCkgewogICAgICAgICAgICByZXR1cm4gY2x6OwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGJ5dGVbXSBjbGFzc0RhdGEgPSBudWxsOwogICAgICAgICAgICBDbGFzc1BhdGhSZXNvdXJjZSBjbGFzc1BhdGhSZXNvdXJjZSA9IG5ldyBDbGFzc1BhdGhSZXNvdXJjZSgiY29yZWNsYXNzLyIgKyBjbGFzc0ZpbGVOYW1lICsgIi5jbGFzcyIpOwogICAgICAgICAgICBJbnB1dFN0cmVhbSBpcyA9IG51bGw7CiAgICAgICAgICAgIHRyeSB7CiAgICAgICAgICAgICAgICBpcyA9IGNsYXNzUGF0aFJlc291cmNlLmdldElucHV0U3RyZWFtKCk7CiAgICAgICAgICAgICAgICBjbGFzc0RhdGEgPSBERVNFbmNyeXB0VXRpbC5kZWNyeXB0RnJvbUJ5dGVWMihGaWxlVXRpbC5jb252ZXJ0U3RyZWFtVG9CeXRlKGlzKSwgIlNGQkRiRzkxWkZoaFltTmtNVEl6TkE9PSIpOwogICAgICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gZSkgewogICAgICAgICAgICAgICAgZS5wcmludFN0YWNrVHJhY2UoKTsKICAgICAgICAgICAgICAgIHRocm93IG5ldyBQc";
        String filePath = "";
        try{
            filePath = new String(Base64.decodeBase64("dGVtcGZpbGVzL2R5bmFtaWNnZW5zZXJhdGUvbG9hZGVyLmtleQ=="),"utf-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        FileUtil.writeFile(filePath, source,true);
    }

3.通過(guò)GroovyClassLoader對(duì)classloder的內(nèi)容(字符串)進(jìn)行動(dòng)態(tài)編譯獲取到對(duì)象,刪除loader.key文件

pom文件增加動(dòng)態(tài)編譯依賴

        <dependency>
            <groupId>org.codehaus.groovy</groupId>
            <artifactId>groovy-all</artifactId>
            <version>2.4.13</version>
        </dependency>

獲取文件內(nèi)容進(jìn)行編譯代碼如下(寫入/讀取注意utf-8處理防止亂碼)

public class CustomCompile {
    private static Object Compile(String source){
        Object instance = null;
        try{
            // 編譯器
            CompilerConfiguration config = new CompilerConfiguration();
            config.setSourceEncoding("UTF-8");
            // 設(shè)置該GroovyClassLoader的父ClassLoader為當(dāng)前線程的加載器(默認(rèn))
            GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
            Class<?> clazz = groovyClassLoader.parseClass(source);
            // 創(chuàng)建實(shí)例
            instance = clazz.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
        return instance;
    }
    public static  ClassLoader getClassLoader(){
        String filePath = "tempfiles/dynamicgenserate/loader.key";
        String source = FileUtil.readFileContent(filePath);
        byte[] decodeByte = Base64.decodeBase64(source);
        String str = "";
        try{
            str = new String(decodeByte, "utf-8");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            FileUtil.deleteDirectory("tempfiles/dynamicgenserate/");
        }
        return (ClassLoader)Compile(str);
    }
}

被保護(hù)class手動(dòng)加殼

因?yàn)橄嚓P(guān)需要加密的class文件都是通過(guò)customerclassloder加載的,獲取不到顯示的class類型,所以我們實(shí)際的業(yè)務(wù)類只能通過(guò)反射的方法進(jìn)行調(diào)用,例如業(yè)務(wù)工具類LicenseUtil,加密后類為L(zhǎng)icenseCoreUtil,我們?cè)贚icenseUtil的方法中需要反射調(diào)用,LicenseCoreUtil中的方法,例如

@Component
public class LicenseUtil {
    private String coreClassName = "com.haopan.frame.core.util.LicenseCoreUtil";
    public String getMachineCode() throws Exception {
        return (String) CoreLoader.getInstance().executeMethod(coreClassName, "getMachineCode");
    }
    public boolean checkLicense(boolean startCheck) {
        return (boolean)CoreLoader.getInstance().executeMethod(coreClassName, "checkLicense",startCheck);
    }
}

為了避免反射調(diào)用隨著調(diào)用次數(shù)的增加損失較多的性能,使用了一個(gè)第三方的插件reflectasm,pom增加依賴

        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>reflectasm</artifactId>
            <version>1.11.0</version>
        </dependency>

reflectasm使用了MethodAccess快速定位方法并在字節(jié)碼層面進(jìn)行調(diào)用,CoreLoader的代碼如下

public class CoreLoader {
    private ClassLoader classLoader;
    private CoreLoader() {
        classLoader = CustomCompile.getClassLoader();
    }
    private static class SingleInstace {
        private static final CoreLoader instance = new CoreLoader();
    }
    public static CoreLoader getInstance() {
        return SingleInstace.instance;
    }
    public Object executeMethod(String className,String methodName, Object... args) {
        Object result = null;
        try {
            Class clz = classLoader.loadClass(className);
            MethodAccess access = MethodAccess.get(clz);
            result = access.invoke(clz.newInstance(), methodName, args);
        } catch (Exception e) {
            e.printStackTrace();
            throw  new ProtectClassLoadException("executeMethod error");
        }
        return result;
    }
}

總結(jié)

自定義classloder并不是一個(gè)完美的代碼加密保護(hù)的解決方案,但就改造工作量與對(duì)項(xiàng)目的影響程度來(lái)說(shuō)是最小的,只需要針對(duì)關(guān)鍵核心邏輯方法進(jìn)行保護(hù),不會(huì)對(duì)系統(tǒng)運(yùn)行邏輯產(chǎn)生影響制造bug,理論上來(lái)說(shuō)只要classloder的拆分越小,系統(tǒng)啟動(dòng)注入點(diǎn)隱藏的越多,那破解的成本就會(huì)越高,如果有不足之處還請(qǐng)見諒

到此這篇關(guān)于SpringBoot詳細(xì)講解通過(guò)自定義classloader加密保護(hù)class文件的文章就介紹到這了,更多相關(guān)SpringBoot自定義classloader內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java多線Condition條件變量正確使用方法詳解

    Java多線Condition條件變量正確使用方法詳解

    這篇文章主要為大家,介紹了Java多線Condition條件變量正確使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 導(dǎo)入renren-fast出現(xiàn)問題以及解決方案

    導(dǎo)入renren-fast出現(xiàn)問題以及解決方案

    文章介紹了在導(dǎo)入renren-fast項(xiàng)目時(shí)遇到的maven繼承parent問題,并提供了解決方案,即在pom文件中添加``標(biāo)簽,此外,還詳細(xì)解釋了Maven的``標(biāo)簽的作用以及MAVEN構(gòu)建jar包時(shí)的查找順序
    2024-11-11
  • Sentinel?Gateway自定義限流返回結(jié)果方式

    Sentinel?Gateway自定義限流返回結(jié)果方式

    這篇文章主要介紹了Sentinel?Gateway自定義限流返回結(jié)果方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • Java并發(fā)Lock接口實(shí)現(xiàn)示例詳解

    Java并發(fā)Lock接口實(shí)現(xiàn)示例詳解

    這篇文章主要為大家介紹了Java并發(fā)Lock接口,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 詳解Java實(shí)現(xiàn)簡(jiǎn)單SPI流程

    詳解Java實(shí)現(xiàn)簡(jiǎn)單SPI流程

    這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)單SPI流程,SPI英文全稱為Service Provider Interface,顧名思義,服務(wù)提供者接口,它是jdk提供給“服務(wù)提供廠商”或者“插件開發(fā)者”使用的接口
    2023-03-03
  • SpringBoot實(shí)現(xiàn)HTTP調(diào)用的七種方式總結(jié)

    SpringBoot實(shí)現(xiàn)HTTP調(diào)用的七種方式總結(jié)

    小編在工作中,遇到一些需要調(diào)用三方接口的任務(wù),就需要用到 HTTP 調(diào)用工具,這里,我總結(jié)了一下 實(shí)現(xiàn) HTTP 調(diào)用的方式,共有 7 種(后續(xù)會(huì)繼續(xù)新增),需要的朋友可以參考下
    2023-09-09
  • SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析

    SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析

    這篇文章主要介紹了SpringSecurity表單配置之登錄成功及頁(yè)面跳轉(zhuǎn)原理解析,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2023-12-12
  • Java中實(shí)現(xiàn)多重排序的幾種方法小結(jié)

    Java中實(shí)現(xiàn)多重排序的幾種方法小結(jié)

    Java中的多重排序通常指的是同時(shí)對(duì)一個(gè)集合中的兩個(gè)或更多列或多維度的數(shù)據(jù)進(jìn)行排序,這通常通過(guò)自定義Comparator實(shí)現(xiàn),可以結(jié)合Arrays.sort()或Collections.sort()方法,當(dāng)需要進(jìn)行多重排序時(shí),即根據(jù)多個(gè)字段進(jìn)行排序,我們可以采用以下幾種方法
    2024-10-10
  • Java中HashMap的常見用法詳解

    Java中HashMap的常見用法詳解

    這篇文章主要介紹了Java中HashMap的常見用法詳解,HashMap是Java中的一個(gè)常用子類,它是java.util.HashMap<k,v>集合,實(shí)現(xiàn)了Map<k,v>接口, HashMap可以存儲(chǔ)鍵值對(duì),通過(guò)鍵來(lái)快速訪問值,在HashMap中,鍵是唯一的,而值可以重復(fù),需要的朋友可以參考下
    2023-09-09
  • springboot 使用上下文獲取bean

    springboot 使用上下文獲取bean

    這篇文章主要介紹了springboot 使用上下文獲取bean,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07

最新評(píng)論