由于项目需求,需要对项目代码做个混淆
在做的过程中发现,搜索到的大部分帖子都是单 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 配置的不合理,需反复检查验证
评论