likes
comments
collection
share

SpringBoot 多Module Proguard混淆(Gradle)

作者站长头像
站长
· 阅读数 13

由于项目需求,需要对项目代码做个混淆

在做的过程中发现,搜索到的大部分帖子都是单Module的和Maven项目的,有一定的借鉴意义,但还是不能直接解决问题。经过一段时间的试错之后,总算把项目代码混淆成功,并运行起来了,在此就做个总结,希望能对也有此需求的大家有所帮助。

1、Proguard介绍

使用文档:www.guardsquare.com/manual/home

ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。可以删除没用的注释,最大限度地优化字节码文件。它还可以使用简短的无意义的名称来重命名已经存在的类、字段、方法和属性。常常用于Android开发用于混淆最终的项目,增加项目被反编译的难度。

ProGuard处理流程:

    • 压缩(Shrink):检测并删除未使用的类,字段,方法和属性。

    • 优化(Optimize):分析并优化方法的字节码。

    • 混淆(Obfuscate): 使用简短的无意义名称例如a,b,c等,重命名类,字段和方法。

    • 预检(Preveirfy):主要是在Java平台上对处理后的代码进行预检。

需要在此说明的是,Proguard只是增加了反编译的难度,并不是真正的加密。

2、SpringBoot 多Module Gradle集成

2.1、引入插件

在项目启动的Module的build.gradle下引入插件

build.gradle参考结构

import proguard.gradle.ProGuardTask

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("com.guardsquare:proguard-gradle:7.3.2")
    }
}

task proguard(type: ProGuardTask) {}

注:ProGuard的版本对JDK的版本有限制,具体可参考官方文档。由于我们项目使用的是JDK17,所以使用的是7.3.2,具体项目具体配置

2.2、规则配置

在上述的build.gradle同级目录下,创建规则文件

proguard.pro

#指定Java的版本
-target 17
#proguard会对代码进行优化压缩,他会删除从未使用的类或者类成员变量等
-dontshrink
#是否关闭字节码级别的优化,如果不开启则设置如下配置
-dontoptimize
#混淆时不生成大小写混合的类名,默认是可以大小写混合
-dontusemixedcaseclassnames
# 对于类成员的命名的混淆采取唯一策略
-useuniqueclassmembernames
#混淆时不生成大小写混合的类名,默认是可以大小写混合
-dontusemixedcaseclassnames
#混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代
-adaptclassstrings

#对异常、注解信息予以保留
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
# 此选项将保存接口中的所有原始名称(不混淆)-->
-keepnames interface ** { *; }
# 此选项将保存所有软件包中的所有原始接口文件(不进行混淆)
#-keep interface * extends * { *; }
#保留参数名,因为控制器,或者Mybatis等接口的参数如果混淆会导致无法接受参数,xml文件找不到参数
-keepparameternames
# 保留枚举成员及方法
-keepclassmembers enum * { *; }
# 不混淆所有类,保存原始定义的注释-
-keepclassmembers class * {
                        @org.springframework.context.annotation.Bean *;
                        @org.springframework.beans.factory.annotation.Autowired *;
                        @org.springframework.beans.factory.annotation.Value *;
                        @org.springframework.stereotype.Service *;
                        @javax.persistence.Table *;
                        @javax.persistence.Entity *;
                        }


-keepclasseswithmembers public class * { public static void main(java.lang.String[]);} ##保留main方法的类及其方法名
-keep public class ch.qos.logback.**{*;}
-keep class com.fasterxml.jackson.** { *; }
-keep public class com.fasterxml.jackson.** { *; }

#忽略warn消息
-ignorewarnings
#忽略note消息
-dontnote
#打印配置信息
-printconfiguration

SpringBoot修改

public class xxApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(xxApplication .class)
                .beanNameGenerator(new CustomBeanNameGenerator())
                .run(args);
    }

    private static class CustomBeanNameGenerator implements BeanNameGenerator {
        @Override
        public String generateBeanName(BeanDefinition d, BeanDefinitionRegistry r) {
            return d.getBeanClassName();
        }
    }
}

注:配置文件是混淆过程中最容易出错的地方,很多时候不容易把握住什么时候该keep,这里简单总结一下

1、涉及全路径的以及较底层类应该保留,如使用了反射或者切面等等

2、对外公开的类应该保留

2.3、Gradle Task

以上涉及的部分单Module和多Module都没有区别,这部分是真正实现SpringBoot 多Module混淆的部分。

在最开始配置的时候,由于没有配置其他Module,导致最后构建的Jar包只包含启动类。

以下还是在上述的build.gradle上做修改,只涉及到ProGuard

build.gradle

import proguard.gradle.ProGuardTask

buildscript {

    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("com.guardsquare:proguard-gradle:7.3.2")
    }

}

dependencies {
    implementation project(':xx:a')
    implementation project(':xx:b')
    implementation project(':xx:c')
    implementation project(':xx:d')
    implementation('com.guardsquare:proguard-gradle:7.3.2')
}

task proguard(type: ProGuardTask) {
    // 输出混淆前->混淆后的映射
    printmapping "$buildDir/mapping.txt"
    // 混淆规则文件
    configuration 'proguard.pro'

    // 混淆时依赖的库
    libraryjars configurations.runtimeClasspath.collect()
    // jdk 依赖,区分jdk8 前后版本
    if (System.getProperty('java.version').startsWith('1.')) {
        libraryjars "${System.getProperty('java.home')}/lib/rt.jar"
    } else {
        libraryjars "${System.getProperty('java.home')}/jmods/java.base.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
        libraryjars "${System.getProperty('java.home')}/jmods/java.desktop.jmod", jarfilter: '!**.jar', filter: '!module-info.class'
    }

    // 混淆输入
    //class 混淆
    injars sourceSets.main.output

    injars "${project(":xx:a").buildDir}\classes\java\main"
    injars "${project(":xx:b").buildDir}\classes\java\main"
    injars "${project(":xx:c").buildDir}\classes\java\main"
    injars "${project(":xx:d").buildDir}\classes\java\main"


    // 混淆输出
    outjars "$buildDir/classes-pro"
}

// 清除现有的lib目录
task clearJar(type: Delete) {
    delete "$buildDir\libs\lib"
}

// 拷贝配置文件
task copyConfigFile(type: Copy) {
    // 清除现有的配置目录
    delete "$buildDir\libs\config"
    from 'src/main/resources'
    into 'build/libs/config'
}

// 将依赖包复制到lib目录
task copyJar(type: Copy, dependsOn: 'clearJar') {
    from configurations.compileClasspath
    into "$buildDir\libs\lib"
}


task clearSdkJar(type: Delete) {
    delete "$buildDir\libs\sdk.jar"
}

tasks.register('makeJar', Jar) {
    //指定生成的jar名
    baseName 'xx'
    from sourceSets.main.output
    
    // lib目录的清除和复制任务
    dependsOn clearJar
    dependsOn copyJar

    // 指定依赖包的路径
    manifest {
        attributes "Manifest-Version": 1.0,
                'Main-Class': 'xx.xx.xx',
                'Class-Path': configurations.compileClasspath.files.collect { "lib/$it.name" }.join(' ')
    }

}

tasks.register('makeProJar', Jar) {

    dependsOn makeJar
    dependsOn proguard

    //指定生成的jar名
    baseName 'xx-pro'
    //从哪里打包class文件
    from("$buildDir/classes-pro")

    dependsOn copyJar

    // 指定依赖包的路径
    manifest {
        attributes "Manifest-Version": 1.0,
                'Main-Class': 'xx.xx.xx',
                'Class-Path': configurations.compileClasspath.files.collect { "lib/$it.name" }.join(' ')
    }

    doLast {
        delete 'build/libs/lib/com'
    }

}

注:proguard中的injars是实现多Module混淆的关键,通过injars将其他Module的代码混淆到指定目录

3、验证

执行gradle clean

执行gradle makeProJar

最后在libs目录下即可看到原Jar包和混淆过后的Jar包,lib目录下为依赖的类库

SpringBoot 多Module Proguard混淆(Gradle)

启动验证:java -jar -Dloader.path=lib xx-pro-1.0-SNAPSHOT.jar

启动不成功的话,可能是proguard.pro配置的不合理,需反复检查验证