使用
proguard
混淆代码只能增加阅读和理解的难度, 并不能百分百保证代码安全。常用的应用场景是项目需要部署到客户机器上,一定程度上防止代码泄露。
proguard 简介
ProGuard 是一个混淆代码的开源项目,它的主要作用是混淆代码,ProGuard 包括以下 4 个功能:
- 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)
- 优化(Optimize):对字节码进行优化,移除无用的指令
- 混淆(Obfuscate):使用 a,b,c,d 这样简短而无意义的名称,对类、字段和方法进行重命名
- 预检(Preveirfy):在 Java 平台上对处理后的代码进行预检,确保加载的 class 文件是可执行的
实战示例
proguard 是广为使用的工具之一,可是用他的客户端方式来混淆 springboot 项目的时候最后总得不到可执行的 jar。后来发现了
proguard-maven-plugin
这个插件,所有 proguard 的指令都可以在 pom 中进行定义实现,本文基于
springboot2.x + maven + proguard
架构进行代码混淆。
源码:
https://github.com/hacfins/spring-boot-2-api
修改 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>${proguard-maven-plugin.version}</version><executions><execution><!--打包的时候开始混淆--><phase>package</phase><goals><goal>proguard</goal></goals></execution></executions><configuration><proguardVersion>${proguard.version}</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><!--不混淆所有类,保存原始定义的注释--><!--<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>${proguard.version}</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 com.langyastudio.edu.admin.Application {
public static void main(java.lang.String[]);
}
#mybatis的mapper/实体类不混淆,否则会导致xml配置的mapper找不到
-keep class com.langyastudio.edu.admin.dao.*
-keeppackagenames com.langyastudio.edu.admin.dao
#考虑到scanBasePackages,需要包名不被修改
-keeppackagenames com.langyastudio.edu
-keeppackagenames com.langyastudio.edu.admin.common
#一些配置类比如datasource,aopconfig如果混淆会导致各种启动报错
# 比如用@Pointcut("execution(public * com.langyastudio.edu.*.controller..*.*(..))")
# 指定webLog方法对应的@Pointcut作为切入点,所以包的名字不能修改
-keeppackagenames com.langyastudio.edu.*.controller.**
-keep class com.langyastudio.edu.admin.config.*
#保留Serializable序列化的类不被混淆
#例如传入/输出的Bean属性
-keepclassmembers class * implements java.io.Serializable {*;}
#保留空的构造函数
#-keepclassmembers class com.hacfin.* {
# public <init>(...);
#}
混淆配置要点
- 建议逐个 java 包定义混淆规则,这样思路更清晰
- repository(dao)层需要保存包名和类名,因为 Mybatis 的 xml 文件中引用了dao 层的接口
- controller 层注意在使用 @PathVariable、@RequestParam 时需要显式声明参数名
- dao 层用于映射数据库表的类和 controller 层映射前台参数的类,都需要保留类成员
- 修改 spring 的 bean 命名策略,改成按类的全限定名来命名
- 等等
入口程序类
- 入口程序类不能混淆,混淆会导致 springboot 启动不了,增加如下配置:
-keep class com.langyastudio.edu.admin.Application {
public static void main(java.lang.String[]);
}
bean 名称冲突
- 默认混淆后的类名为 xx.a.b、xx.c.a,直接使用混淆后的类名作为 bean 会引发重名异常,所以需要修改 BeanName 生成策略。
不能重写 generateBeanName 方法,因为有些 Bean 会自定义 BeanName,所以这些情况还需要走原来的逻辑。
publicclassApplication{publicstaticvoidmain(String[] args){newSpringApplicationBuilder(Application.class).beanNameGenerator(newUniqueNameGenerator()).run(args);}/**
* 由于需要混淆代码,混淆后类都是A B C,spring 默认是把A B C当成BeanName,BeanName又不能重复导致报错
* 所以需要重新定义BeanName生成策略
*/@Component("UniqueNameGenerator")publicstaticclassUniqueNameGeneratorextendsAnnotationBeanNameGenerator{/**
* 重写buildDefaultBeanName
* 其他情况(如自定义BeanName)还是按原来的生成策略,只修改默认(非其他情况)生成的BeanName带上包名
*/@Overridepublic@NotNullStringbuildDefaultBeanName(BeanDefinition definition){//全限定类名returnObjects.requireNonNull(definition.getBeanClassName());}}}
包名保留
- 考虑到
scanBasePackages
等特殊的注解配置,需要包名不被修改,配置如下:
-keeppackagenames com.langyastudio.edu
scanBasePackages
样例:
如果
com.langyastudio.edu
名称被混淆,将导致
scanBasePackages
失效
@SpringBootApplication(scanBasePackages ={"com.langyastudio.edu.*"})publicclassApplication{publicstaticvoidmain(String[] args){
xxxxx
}}
配置类
- 一些配置类比如 datasource、aop、config 等如果混淆会导致各种启动报错比如用
@Pointcut("execution(public * com.langyastudio.edu.*.controller..*.*(..))")
指定webLog
方法对应的 @Pointcut 作为切入点。所以包的名字与函数名称不能修改-keeppackagenames com.langyastudio.edu.*.controller.**-keep class com.langyastudio.edu.admin.config.*
- 配置类样例:
publicclassWebLogAspect{/** * 包及其子包下所有类中的所有方法都应用切面里的通知 */@Pointcut("execution(public * com.langyastudio.edu.*.controller..*.*(..))")publicvoidwebLog(){}@Before("webLog()")publicvoiddoBefore(JoinPoint joinPoint)throwsThrowable{}}
dao 层
- mybatis 的 mapper/实体类 不能混淆,否则会导致 xml 配置的 mapper 找不到
-keep class com.langyastudio.edu.admin.dao.*-keeppackagenames com.langyastudio.edu.admin.dao#接口类保留xxxx
bean 属性保留
- controller 层映射前台参数的类、后端返回的 bean 属性类等,不能混淆类的成员属性(如变成
string a;
) - 修改方案为保留
Serializable
序列化的类成员不被混淆-keepclassmembers class * implements java.io.Serializable {*;}
- bean 样例:需要将原有的属性类增加
Serializable
的继承
@Data@NoArgsConstructorpublicclassTokenVOimplementsSerializable{/**
* token
*/privateString token;/**
* token 前缀
*/privateString tokenHead;}
编译打包
打包成功一定要运行 编译后的 jar ,测试是否能否正常运行
mvnw clean package -Dmaven.test.skip=true
混淆效果
用于进一步查看打包后的 jar 文件是否符合要求,同时还可以辅助查找 jar 文件运行失败的原因
常见的反编译工具使用
jd-gui
。下载地址:http://java-decompiler.github.io/。
直接通过
jd-gui
窗口打开编译打包后的 jar 文件即可。
参考文档
ProGuard manual
Code obfuscation for Spring Boot applications using the ProGuard plugin proguard-maven-plugin
springboot proguard 代码混淆
版权归原作者 郎涯技术 所有, 如有侵权,请联系我们删除。