由于项目需求,需要对项目代码做个混淆
在做的过程中发现,搜索到的大部分帖子都是单 Module 的和 Maven 项目的,有一定的借鉴意义,但还是不能直接解决问题。经过一段时间的试错之后,总算把项目代码混淆成功,并运行起来了,在此就做个总结,希望能对也有此需求的大家有所帮助。
1、Proguard 介绍
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 目录下为依赖的类库
启动验证:java -jar -Dloader.path=lib xx-pro-1.0-SNAPSHOT.jar
启动不成功的话,可能是 proguard.pro 配置的不合理,需反复检查验证
评论