当前位置:   article > 正文

proguard+springboot+maven插件 代码混淆_proguard-maven-plugin

proguard-maven-plugin

一 背景

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

   

二 步骤

2.1 导入maven插件

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

  1. <plugin>
  2. <groupId>com.github.wvengen</groupId>
  3. <artifactId>proguard-maven-plugin</artifactId>
  4. <version>2.0.11</version>
  5. <executions>
  6. <execution>
  7. <phase>package</phase>
  8. <goals>
  9. <goal>proguard</goal>
  10. </goals>
  11. </execution>
  12. </executions>
  13. <configuration>
  14. <proguardVersion>2.0.11</proguardVersion>
  15. <injar>${project.build.finalName}.jar</injar>
  16. <outjar>${project.build.finalName}.jar</outjar>
  17. <obfuscate>true</obfuscate>
  18. <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>
  19. <libs>
  20. <!-- Include main JAVA library required.-->
  21. <lib>${java.home}/lib</lib>
  22. <!-- Include crypto JAVA library if necessary.-->
  23. <!--<lib>${java.home}/lib/jce.jar</lib>-->
  24. </libs>
  25. <options>
  26. <!-- JDK目标版本1.8-->
  27. <option>-target 1.8</option>
  28. <!-- 不做收缩(删除注释、未被引用代码)-->
  29. <option>-dontshrink</option>
  30. <!-- 不做优化(变更代码实现逻辑)-->
  31. <option>-dontoptimize</option>
  32. <!-- 不路过非公用类文件及成员-->
  33. <option>-dontskipnonpubliclibraryclasses</option>
  34. <option>-dontskipnonpubliclibraryclassmembers</option>
  35. <!--不用大小写混合类名机制-->
  36. <option>-dontusemixedcaseclassnames</option>
  37. <!-- 优化时允许访问并修改有修饰符的类和类的成员 -->
  38. <option>-allowaccessmodification</option>
  39. <!-- 确定统一的混淆类的成员名称来增加混淆-->
  40. <option>-useuniqueclassmembernames</option>
  41. <!-- 不混淆所有包名-->
  42. <option>-keeppackagenames</option>
  43. <!-- 混淆类名之后,对使用Class.forName('className')之类的地方进行相应替代 -->
  44. <option>-adaptclassstrings</option>
  45. <!--忽略警告信息-->
  46. <option>-ignorewarnings</option>
  47. <option>-printseeds</option>
  48. <option>-keepattributes
  49. Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod
  50. </option>
  51. <!-- 不混淆所有的set/get方法-->
  52. <option>-keepclassmembers public class * {void set*(***);*** get*();}</option>
  53. <!-- 不混淆main的信息-->
  54. <option>-keepclasseswithmembers public class * { public static void main(java.lang.String[]);}</option>
  55. <!-- 不混淆枚举方法的信息-->
  56. <option>-keepclassmembers enum * { *; }</option>
  57. </options>
  58. </configuration>
  59. <dependencies>
  60. <dependency>
  61. <groupId>net.sf.proguard</groupId>
  62. <artifactId>proguard-base</artifactId>
  63. <version>6.2.2</version>
  64. </dependency>
  65. </dependencies>
  66. </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构建的结果。
  1. <plugin>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-maven-plugin</artifactId>
  4. <executions>
  5. <execution>
  6. <goals>
  7. <goal>repackage</goal>
  8. </goals>
  9. </execution>
  10. </executions>
  11. </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来获取,就会产生异常。

  1. public class ProGuardBeanNameGenerator extends AnnotationBeanNameGenerator {
  2. /**
  3. * 重写buildDefaultBeanName
  4. * 其他情况(如自定义BeanName)还是按原来的生成策略,只修改默认(非其他情况)生成的BeanName带上 包名
  5. *
  6. */
  7. @Override
  8. public String buildDefaultBeanName(BeanDefinition definition) {
  9. if("xxx".equals(definition.getBeanClassName())){
  10. String beanClassName = definition.getBeanClassName();
  11. Assert.state(beanClassName != null, "No bean class name set");
  12. String shortClassName = ClassUtils.getShortName(beanClassName);
  13. return Introspector.decapitalize(shortClassName);
  14. }
  15. return definition.getBeanClassName();
  16. }
  17. }

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

  1. @EnableCaching
  2. @EnableScheduling
  3. @SpringBootApplication
  4. public class Application {
  5. public static void main(String[] args) {
  6. new SpringApplicationBuilder(Application.class)
  7. .beanNameGenerator(new ProGuardBeanNameGenerator())
  8. .run(args);
  9. }
  10. }

2.4.2 @Bean注解生成的bean问题

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

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

  1. <option>-keep class com.xxx.** {
  2. *;
  3. }
  4. </option>

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

  1. <option>-keep class * {
  2. @org.springframework.context.annotation.Bean *;
  3. }
  4. </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 结束

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

❤❤❤

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/秋刀鱼在做梦/article/detail/810266
推荐阅读
相关标签
  

闽ICP备14008679号