jar包代码混淆
在maven项目中使用插件直接混淆之后再打包
修改 pom 文件
定义 proguard-maven-plugin 插件且插件位于 spring-boot-maven-plugin 插件的前面。
-
proguardInclude
表示使用外部扩展的配置文件 proguard.cfg,和 pom.xml 同目录
-
keepparameternames
此选项将保留所有原始方法参数,controller 如果函数的参数也混淆(如混淆为a、b、c等)会导致传参映射不上
详细配置如下(由于有详细注释,不再一一说明):
<build>
<plugins>
<!--proguard混淆插件-->
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.6.0</version>
<executions>
<execution>
<!--打包的时候开始混淆-->
<phase>package</phase>
<goals>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<proguardVersion>7.2.2</proguardVersion>
<injar>${project.build.finalName}.jar</injar>
<!--输出的jar-->
<outjar>${project.build.finalName}.jar</outjar>
<!--是否混淆-->
<obfuscate>true</obfuscate>
<proguardInclude>${basedir}/proguard.cfg</proguardInclude>
<options>
<!--默认开启,不做收缩(删除注释、未被引用代码)-->
<option>-dontshrink</option>
<!--默认是开启的,这里关闭字节码级别的优化-->
<option>-dontoptimize</option>
<!--对于类成员的命名的混淆采取唯一策略-->
<option>-useuniqueclassmembernames</option>
<!--混淆时不生成大小写混合的类名,默认是可以大小写混合-->
<option>-dontusemixedcaseclassnames </option>
<!--混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代-->
<option>-adaptclassstrings</option>
<!--对异常、注解信息在runtime予以保留,不然影响springboot启动-->
<option>-keepattributes
Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod
</option>
<!--此选项将保存接口中的所有原始名称(不混淆)-->
<option>-keepnames interface ** { *; }</option>
<!--此选项将保存所有软件包中的所有原始接口文件(不进行混淆)-->
<!--<option>-keep interface * extends * { *; }</option>-->
<!--此选项将保留所有原始方法参数,controller如果参数也混淆会导致传参映射不上 -->
<option>-keepparameternames</option>
<!--保留枚举成员及方法-->
<option>-keepclassmembers enum * { *; }</option>
<!--防止mybatis resultType 找不到bean-->
<option>-keep class cn.fuzhi.model.** { *; }</option>
<option>-keep class cn.fuzhi.config.** { *; }</option>
<option>-keep class cn.fuzhi.task.** { *; }</option>
<!--不混淆所有类,保存原始定义的注释-->
<!--<option>-keepclassmembers class * {
@org.springframework.context.annotation.Bean *;
@org.springframework.beans.factory.annotation.Autowired *;
@org.springframework.beans.factory.annotation.Value *;
@org.springframework.stereotype.Service *;
@org.springframework.stereotype.Component *;
}
</option>-->
<!--忽略warn消息-->
<option>-ignorewarnings</option>
<!--忽略note消息-->
<option>-dontnote</option>
</options>
<!--java 11-->
<!-- <libs>-->
<!-- <lib>${java.home}/jmods/</lib>-->
<!-- </libs>-->
<!--java 8-->
<libs>
<lib>${java.home}/lib/rt.jar</lib>
<lib>${java.home}/lib/jsse.jar</lib>
</libs>
</configuration>
<dependencies>
<dependency>
<groupId>com.guardsquare</groupId>
<artifactId>proguard-base</artifactId>
<version>7.2.2</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!--jar可直接运行-->
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
proguard.cfg
作为 pom.xml 中的扩展配置,详细配置如下:
#所有类(包括接口)的方法参数不混淆(包括没被keep的),如果参数混淆了,mybatis的mapper参数绑定会出错(如#{id})
-keepattributes MethodParameters
#入口程序类不能混淆,混淆会导致springboot启动不了
-keep class cn.fuzhi.Application {
public static void main(java.lang.String[]);
}
#mybatis的mapper/实体类不混淆,否则会导致xml配置的mapper找不到
-keep class cn.fuzhi.mapper.*
-keeppackagenames cn.fuzhi.mapper
#考虑到scanBasePackages,需要包名不被修改
-keeppackagenames cn.fuzhi
#一些配置类比如datasource,aopconfig如果混淆会导致各种启动报错
# 比如用@Pointcut("execution(public * com.langyastudio.edu.*.controller..*.*(..))")
# 指定webLog方法对应的@Pointcut作为切入点,所以包的名字不能修改
-keeppackagenames cn.fuzhi.controller.**
-keep class cn.fuzhi.task.*
#保留Serializable序列化的类不被混淆
#例如传入/输出的Bean属性
-keepclassmembers class * implements java.io.Serializable {*;}
#保留空的构造函数
-keepclassmembers class com.fuzhi.* {
public <init>(...);
}
混淆配置要点
-
建议逐个 java 包定义混淆规则,这样思路更清晰
-
repository(dao)层需要保存包名和类名,因为 Mybatis 的 xml 文件中引用了dao 层的接口
-
controller 层注意在使用 @PathVariable、@RequestParam 时需要显式声明参数名
-
dao 层用于映射数据库表的类和 controller 层映射前台参数的类,都需要保留类成员
-
修改 spring 的 bean 命名策略,改成按类的全限定名来命名
-
等等
入口程序类
入口程序类不能混淆,混淆会导致 springboot 启动不了,增加如下配置:
-keep class cn.fuzhi.Application {
public static void main(java.lang.String[]);
}
bean 名称冲突
默认混淆后的类名为 xx.a.b、xx.c.a,直接使用混淆后的类名作为 bean 会引发重名异常,所以需要修改 BeanName 生成策略。
不能重写 generateBeanName 方法,因为有些 Bean 会自定义 BeanName,所以这些情况还需要走原来的逻辑。
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.beanNameGenerator(new UniqueNameGenerator())
.run(args);
// SpringApplication.run(Application.class, args);
log.error("============Web application Start Success====port:" + Global.System.SERVER_PORT + "===" + Global.System.EV + "=============");
}
@Component("UniqueNameGenerator")
public @NotNull
static class UniqueNameGenerator extends AnnotationBeanNameGenerator {
@Override
public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
return Objects.requireNonNull(definition.getBeanClassName());
}
}
包名保留
-
考虑到
scanBasePackages
等特殊的注解配置,需要包名不被修改,配置如下: -
<!--此选项将保留所有原始方法参数,controller如果参数也混淆会导致传参映射不上 --> <option>-keepparameternames</option>
-
scanBasePackages
样例:
如果 cn
fuzhi 名称被混淆,将导致 scanBasePackages
失效
@SpringBootApplication(scanBasePackages = {"cn.fuzhi.*"})
public class Application
{
public static void main(String[] args)
{
xxxxx
}
}
配置类
一些配置类比如 datasource、aop、config 等如果混淆会导致各种启动报错
比如用 @Pointcut("execution(public * cn.fuzhi.controller.*.*(..))") 指定 ControllerAspect 方法对应的 @Pointcut 作为切入点。所以包的名字与函数名称不能修改
@Pointcut("execution(public * cn.fuzhi.controller.*.*(..)))")
public void ControllerAspect(){
}
dao 层
mybatis 的 mapper/实体类 不能混淆,否则会导致 xml 配置的 mapper 找不到
-keep class cn.fuzhi.mapper.*
-keeppackagenames cn.fuzhi.mapper
#接口类保留
xxxx
bean 属性保留
controller 层映射前台参数的类、后端返回的 bean 属性类等,不能混淆类的成员属性(如变成 string a;)
修改方案为保留 Serializable 序列化的类成员不被混淆
-keepclassmembers class * implements java.io.Serializable {*;}
bean 样例:
需要将原有的属性类增加 Serializable 的继承
@Data
@NoArgsConstructor
public class TokenVO implements Serializable
{
/**
* token
*/
private String token;
/**
* token 前缀
*/
private String tokenHead;
}
编译打包
mvnw clean package -Dmaven.test.skip=true
混淆效果
用于进一步查看打包后的 jar 文件是否符合要求,同时还可以辅助查找 jar 文件运行失败的原因
常见的反编译工具使用 jd-gui
。下载地址:http://java-decompiler.github.io/。
直接通过 jd-gui
窗口打开编译打包后的 jar 文件即可。
混淆常见问题
混淆后自己全部代码没有被放入混淆后的jar包里(jar\BOOT-INF\classes 里面不包含com)
原因: proguard-maven-plugin插件放到了spring-boot-maven-plugin插件后面,应该是因为spring-boot-maven-plugin放在前面会先执行spring-boot-maven-plugin的repackage再执行proguard-maven-plugin的混淆,混淆后没有重新repackage
解决:把proguard-maven-plugin插件放到spring-boot-maven-plugin前面
DataSource,Aop,swagger等相关配置类混淆后导致的运行报错
原因:比如Aop是因为用@Around(value = "apiLog()")指定apiLog方法对应的@Pointcut作为切入点,但是因为apiLog方法被混淆成a导致找不到对应apiLog方法。就不一一列举了
解决:我是直接把所有配置类放到framework下面,然后proguard-maven-plugin配置让framework 下面的类和方法全部不混淆,这样省事点
-keep class cn.fuzhi.task.** {
*;
}
aop事务控制,事务失效问题
原因:因为aop pointcut 是根据包路径和方法名(update,find)拦截,service 混淆后 包路径和方法名都变成 a,b导致拦截不到
解决:不混淆包路径和方法public方法名,如下
#不混淆service下的public方法名
-keepclassmembers class cn.fuzhi.service.** {public *** *(...);}
#不混淆service包名
-keeppackagenames cn.fuzhi.service.**
swagger 文档看不到api,以及接口请求参数controller 参数绑定报错
1)swagger 文档看不到api:因为我swagger配置的api 是通过.apis(RequestHandlerSelectors.basePackage(packageName))指定了具体包名,controller混淆后包名变成a,b,c所以扫描不到接口自然就生成不了文档
2)api接口请求出错:spring接口参数绑定是根据参数名称,混淆后参数名称会变成var1等,绑定的时候找不到原来定义的参数名就报错了.
解决:proguard-maven-plugin配置所有controller public 方法不参与混淆以及参数也不参与混淆,如下
#所有controller类的public方法你参与混淆
<option>-keepclassmembers class cn.fuzhi.controller.* {public *** *(...);}</option>
#不混淆controller的包路径
<option>-keeppackagenames cn.fuzhi.controller</option>
#参数不参与混淆
<option>-keepparameternames</option>
spring controller 参数绑定问题
原因:spring参数绑定有些是根据controller的方法参数名称绑定的,参数名称混淆后就会绑定不上
解决:
方法1:Controller用如@RequestParam的方式绑定
方法2:
①.配置-keepparameternames,这个参数可以让不被混淆的方法也不会混淆其参数,对interface无效.
<option>-keepparameternames</option>
②. 因为第①步的配置,那只要配置controller里的方法不混淆,那么方法参数也就不会混淆了,如下
-keepclassmembers class cn.fuzhi.controller.* {public *** *(...);}
mybatis 参数绑定报错org.apache.ibatis.binding.BindingException
原因:和第6点类似,因为方法参数被混淆,所以导致mybatis的mapper(是interface)定义的接口参数Id被混淆成 var1,mybatis xml #{id}绑定的时候只能找到var1于是BindingException.但是不能用第6点的方法2,因为keepparameternames 能让keep的class参数不混淆,却不能让interface的参数不参与混淆,而mybatis的mapper就是interface
解决:
方法1:mapper的参数 用 @Param("id") 强行绑定
方法2:此方法也可以解决第6点的问题
①. 配置 -keepattributes MethodParameters ,这个参数可以让不管有没有参与混淆的类的方法参数都不参与混淆,对类和接口都生效,但是这样会导致即使参与混淆的类也无法混淆参数
-keepattributes MethodParameters
②. 上面的配置是可以让所有方法参数不被proguard混淆,但是有些项目在java编译的时候却可能会对方法参数混淆(好像是针对interface方法参数的混淆,不针对class).所以可以配置编译时不混淆方法参数(-parameters),如下所示(这个根据自己项目实际情况配置,好像是和idea版本有关系)
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<compilerArgument>-parameters</compilerArgument>
</configuration>
</plugin>
CreateProcess error=206, 文件名或扩展名太长。
原因:因为window的cmd有长度限制,而proguard混淆时依赖太多的jar包导致命令行过长
解决:在proguard-maven-plugin的configuration中加入下面配置,这个配置会把jar包放到临时目录以便缩短命令行
<putLibraryJarsInTempDir>true</putLibraryJarsInTempDir>