一 背景

Java web项目部署到服务器上以后,尤其针对是在客户的服务器上部署,很容易被“友商”捞到相关的包,通过反编译的手段,我们的代码几乎等同于裸奔在不可管控的服务器上,产品的设计和代码细节都被一览无余,所以针对给厂商做的服务,我们做一些代码的混淆是很有必要的。

二 步骤

2.1 导入maven插件

通过maven插件的办法的好处就是,在编译过程中混淆代码,尽量避免了给业务上带来的影响。这里混淆代码如下,实际混淆过程中要根据实际业务调整

          <plugin><groupId>com.github.wvengen</groupId><artifactId>proguard-maven-plugin</artifactId><version>2.0.11</version><executions><execution><phase>package</phase><goals><goal>proguard</goal></goals></execution></executions><configuration><proguardVersion>2.0.11</proguardVersion><injar>${project.build.finalName}.jar</injar><outjar>${project.build.finalName}.jar</outjar><obfuscate>true</obfuscate><proguardInclude>${project.basedir}/proguard.cfg</proguardInclude><libs><!-- Include main JAVA library required.--><lib>${java.home}/lib</lib><!-- Include crypto JAVA library if necessary.--><!--<lib>${java.home}/lib/jce.jar</lib>--></libs><options><!-- JDK目标版本1.8--><option>-target 1.8</option><!-- 不做收缩(删除注释、未被引用代码)--><option>-dontshrink</option><!-- 不做优化(变更代码实现逻辑)--><option>-dontoptimize</option><!-- 不路过非公用类文件及成员--><option>-dontskipnonpubliclibraryclasses</option><option>-dontskipnonpubliclibraryclassmembers</option><!--不用大小写混合类名机制--><option>-dontusemixedcaseclassnames</option><!-- 优化时允许访问并修改有修饰符的类和类的成员 --><option>-allowaccessmodification</option><!-- 确定统一的混淆类的成员名称来增加混淆--><option>-useuniqueclassmembernames</option><!-- 不混淆所有包名--><option>-keeppackagenames</option><!-- 混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代 --><option>-adaptclassstrings</option><!--忽略警告信息--><option>-ignorewarnings</option><option>-printseeds</option><option>-keepattributesExceptions,InnerClasses,Signature,Deprecated,SourceFile,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod</option><!-- 不混淆所有的set/get方法--><option>-keepclassmembers public class * {void set*(***);*** get*();}</option><!-- 不混淆main的信息--><option>-keepclasseswithmembers public class * { public static void main(java.lang.String[]);}</option><!-- 不混淆枚举方法的信息--><option>-keepclassmembers enum * { *; }</option></options></configuration><dependencies><dependency><groupId>net.sf.proguard</groupId><artifactId>proguard-base</artifactId><version>6.2.2</version></dependency></dependencies></plugin>

2.2 调整spring-boot-maven-plugin

在使用proguard完成代码混淆的工作后,我们需要构建相关的jar包为可运行的目录结构,这里需要对spring-boot-maven-plugin插件做调整,设置打包策略为repackage,并且将spring-boot-maven-plugin插件位置置于proguard插件之后,这里的原因主要有两个:

  1. proguard混淆代码是在compile和package之后,我们希望在这个过程中,将编译好的class文件能进行混淆,也能够保证最终输出的jar是我们构建的可运行的jar包
  2. proguard混淆插件如果在springboot后使用,那么我们会先将编译好的文件生成jar包,然后repackage,通过springboot-plugin插件构建出最终可运行的jar的目录结构,然后对之前编译好的内容进行混淆,这样会导致覆盖掉之前springboot构建的结果。
<plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><executions><execution><goals><goal>repackage</goal></goals></execution></executions>
</plugin>

2.3 项目构建

mvn package -Dmaven.test.skip=true

通过maven完成项目的构建过程,可以通过在target下生成的proguard_map.txt文件,查看混淆前后的映射关系

2.4 项目运行中出现的问题

2.4.1 non-compatible bean definition of same name and class [a]

ConflictingBeanDefinitionException: Annotation-specified bean name 'a' for bean class [a] conflicts with existing, non-compatible bean definition of same name and class [a]

在proguard混淆代码后,在同一个包下会根据类名生成混淆后的类名,比如a.class ,b.class , c.class等,而通过反编译查看类可知,当我们在其他地方引用时(非同包下),会使用全类名的方式进行引用,这样就避免了使用时出现的问题,而不同的包下的同类名使用时也不会出现任何问题。

springboot容器启动失败的原因是,在使用到@Controller @Service @Component @Repository等注解时,如果不特别指定value属性,那么默认使用的是类名首字母小写的方式注入到容器中作为beanName的,我们默认的混淆规则在同一个包下根据类名长度会生成出诸如 a.class,b.class等的类名 ,这样在作为beanName时就会产生冲突。因为在容器中beanName要求是唯一的,在不考虑业务的情况下我们可以配置@Primary决定优先使用具体哪个,或者是配置spring参数可以为覆盖的方式。但是都不是混淆后的代码的解决办法。

解决办法:

这里采用了自定义BeanNameGenerator的方式强制将beanName重写为全类名,这样加入包名后,在容器就是唯一的beanName。下面的方式是一种模板,这里"xxx"主要针对于某些引入的jar包,可能会由于beanName使用全类名的方式,而在beanFactory中的一级缓存中获取bean实例时,还是采用旧的方式生成的beanName来获取,就会产生异常。


public class ProGuardBeanNameGenerator extends AnnotationBeanNameGenerator {/*** 重写buildDefaultBeanName* 其他情况(如自定义BeanName)还是按原来的生成策略,只修改默认(非其他情况)生成的BeanName带上 包名**/@Overridepublic String buildDefaultBeanName(BeanDefinition definition) {if("xxx".equals(definition.getBeanClassName())){String beanClassName = definition.getBeanClassName();Assert.state(beanClassName != null, "No bean class name set");String shortClassName = ClassUtils.getShortName(beanClassName);return Introspector.decapitalize(shortClassName);}return definition.getBeanClassName();}}

在启动类Application,将自定义beanNameGenerator引入。


@EnableCaching
@EnableScheduling
@SpringBootApplication
public class Application {public static void main(String[] args) {new SpringApplicationBuilder(Application.class).beanNameGenerator(new ProGuardBeanNameGenerator()).run(args);}}

2.4.2 @Bean注解生成的bean问题

springboot同样存在问题的还有Configuration注解下自定义生成的Bean,默认情况下如果我们不增加value属性,那么生成的Bean的beanName为方法名,在代码混淆后,会出现混淆名称重复的问题,和之前的类的名称冲突类似,我们的类中的名称也会出现a,b,c等method。为了避免这样的问题。提供两种方式去改进。

2.4.2.1 通过proguard插件的选项根据包名排除@Configuration的类

<option>-keep class com.xxx.** {*;}
</option>

2.4.2.2 通过proguard插件直接排除@Configuration下的bean注解

<option>-keep class * {@org.springframework.context.annotation.Bean  *;}
</option>

2.4.2.3 通过proguard插件排除包含@Configuration包名下的方法名

<option>-keepclassmembers class com.xxx.** {** *;} </option>

2.4.3 @Async 可能会出现循环依赖的问题

Error creating bean with name 'xxx': Bean with name 'xxx' has been injected into other beans [xxx] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.

由于别的bean在引用时没有使用到最终版本的类,所以抛出异常

这里没有最终版本的类指的是,就类似于如果只存在二级缓存,那么就无法在生成代理类和属性注入时,保持使用的是统一的代理类 的道理是一样的。

这里最简单的办法就是使用懒加载,如果两个Bean真的有依赖的关系 ,就通过懒加载@Lazy去解决。保证了在注入属性时,避免互相依赖的问题。

2.4.4 数据库实体类&& 参数

这里主要针对的是常用的Mybatis和 Mybatis-plus,在使用到实体类时,由于我们是通过ORM,也就是对象关系映射,来完成数据库字段和属性的映射的,那么我们对于entity的排除就是非常有必要的。

我们在写mapper的接口时,偶尔会出现入参不加@Param注解的问题,在以往编译中,我们的mapper中的入参的参数名是保留的(这里需要提及一点,这里仅针对mapper的入参,其他的参数名称默认情况下我们是不能通过反射的方式获取到的,也就是在编译后,参数名称丢失了),但是实际混淆后,我们的参数名称也会丢失,这里就会出现一种问题,那就是在写mapper.xml时的sql时是无法注入参数的,因为名称已经丢失,所以这里希望可以按照标准的方式@Param标识Mybatis接口的参数

2.4.5 no converter for type  序列化的问题

出现这类型的问题,建议是通过混淆选项options的方式将其排除在外,我们混淆的目的是让人无法通过反编译轻松读懂我们的代码,虽然各种问题可以通过微调处理,但实际就我的理解而言,是不必追求混淆的极限的。

2.5 运行

我们的项目运行起来以后,对项目的影响可以说是微乎其微,既尽量保证了没有将混淆的内容耦合到我们的业务代码中,也保证我们通过springboot插件最终输出的可运行jar结构是稳定不变的。不管最终是通过容器运行,还是通过java的命令运行,都可以顺利的执行起来。

对于上述提到的标签,我附一个官方文档,用于方便查询

ProGuard官方文档

3 结束

在解决完混淆中引入的问题后,这样的项目通过反编译,会产生出很难理解的一些名称,帮助到我们保护代码。这里要补充一点,如果是我们自己的测试环境还是建议大家关闭混淆,否则你在查看日志时就是一堆看不懂的内容,我们的代码连行号都不显示了。这样做完以后,就大功告成了!

❤❤❤

proguard+springboot+maven插件 代码混淆相关推荐

  1. java maven 混淆_使用proguard实现maven工程代码混淆

    proguard简单来说是为了防止反编译,更准确的说,是使得代码易读性变差. maven pom配置中加入以下: com.github.wvengen proguard-maven-plugin 2. ...

  2. springboot项目代码混淆和反编译教程·附软件连接

    对springboot项目进行代码混淆,可以防止别人通过反编译项目查看代码,即使反编译了查看的也是混淆后的看不懂的代码. 一定程度保证了项目源码安全性. 下面分享代码混淆步骤和反编译操作 Allato ...

  3. Android开发实践:利用ProGuard进行代码混淆

    由于Android的代码大都是Java代码,所以挺容易被反编译的,好在Android ADT为我们集成了混淆代码的工具,一来可以混淆我们的代码,让程序被反编译后基本看不懂,另外还能起到代码优化的作用. ...

  4. android加密墙,Android代码混淆加密配置(Proguard文件解析)

    Android代码混淆加密配置(Proguard文件解析) Android代码混淆加密配置(Proguard文件解析) 为了防止自己的APP被轻易反编译,我们需要对APK进行混淆,或者特殊加密处理.可 ...

  5. 构建maven项目插件_如何构建一个Maven插件

    构建maven项目插件 使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护. 今天尝试Okta. 由于其插件生态系统的普 ...

  6. 如何构建一个Maven插件

    使用Okta的身份管理平台轻松部署您的应用程序 使用Okta的API在几分钟之内即可对任何应用程序中的用户进行身份验证,管理和保护. 今天尝试Okta. 由于其插件生态系统的普及,Apache Mav ...

  7. Android Studio 代码混淆

    2019独角兽企业重金招聘Python工程师标准>>> 在Android studio 进行代码混淆配置. proguard 配置 -keepclasseswithmembers 指 ...

  8. android开发笔记之 Android代码混淆打包

    大家应该都听过代码混淆吧,如果大家有去反编译过别人的APK的话,应 该会看到好多包名和类名是a,b.c-.之类的的吧,这里就提到了一个概念: 混淆. 那就让我们了解下这个东西吧 作用:为了防止自己的劳 ...

  9. springboot proguard 代码混淆

    前面主要描述碰到的问题,文章后面会给出我的proguard-maven-plugin插件的完整配置 proguard-maven-plugin 插件版本 2.3.1,proguard-base版本7. ...

最新文章

  1. 园区医保和其他地方的医保的区别
  2. 利达主机联网接线端子_门禁主机接门锁和开门按钮方法
  3. 【Java面试题】提取不重复的整数
  4. 带防夹功能的升降器原理_全系标配行车自动落锁功能,全新凯美瑞表现分析
  5. Python 程序员经常犯的 10 个错误
  6. windows socket 简单实例
  7. c语言模拟试卷答案,C语言模拟试卷及其答案
  8. spring cloud config的bootstrap.yml与application.proterties的区别
  9. java: 代码过长_给初学Java,知道这4点太重要了!
  10. Kotlin——高级篇(四):集合(Array、List、Set、Map)基础
  11. C++题解:百钱买百鸡数量
  12. linux强行退出线程,Linux 多线程编程--线程退出
  13. 苹果手表出现,请在iphone 打开apple watch 应用,前生Passcode,轻点密码重试
  14. 机器学习——卷积神经网络(CNN)
  15. imagej得到灰度图数据_教你用免费软件Image J对WB结果进行灰度分析!
  16. mac 远程连接 Windows 桌面
  17. wget 和scp对比_如何下载scp、wget、inotify及如何偷包
  18. Dubbo学习笔记:No provider available for the service ...异常问题的解决
  19. python 简单方式红绿灯状态识别
  20. Android之手机卫士涉及的知识点总结

热门文章

  1. 我的世界java肥料桶_我的世界肥料桶怎么用
  2. farpoint 小数保留4位_FarPoint FpSpread控件的使用心得2
  3. 超链接的hideFocus属性
  4. websocket深入浅出
  5. 虚拟乐器合集 – Steinberg Absolute 4 VST Instrument Collection
  6. csgo下方各种数据都是意思_V社被拳头吓到了?CSGO一个月三次更新,加入特殊击杀标志...
  7. buuctf密码题 传统知识+古典密码
  8. 根据用户生日算出当前年龄,周岁
  9. 想学python制作脚本_十分钟利用Python制作属于你自己的个性logo
  10. 数据库中between的使用方法