springboot+proguard+maven 实现代码混淆 看这一篇就够了

使用 proguard 混淆代码只能增加阅读和理解的难度, 并不能百分百保证代码安全。常用的应用场景是项目需要部署到客户机器上,一定程度上防止代码泄露。

proguard 简介

ProGuard 是一个混淆代码的开源项目,它的主要作用是混淆代码,ProGuard 包括以下 4 个功能:

  • 压缩(Shrink):检测并移除代码中无用的类、字段、方法和特性(Attribute)

  • 优化(Optimize):对字节码进行优化,移除无用的指令

  • 混淆(Obfuscate):使用 a,b,c,d 这样简短而无意义的名称,对类、字段和方法进行重命名

  • 预检(Preveirfy):在 Java 平台上对处理后的代码进行预检,确保加载的 class 文件是可执行的

img

实战示例

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,所以这些情况还需要走原来的逻辑。

public class Application
{
    public static void main(String[] args)
    {
        new SpringApplicationBuilder(Application.class)
                .beanNameGenerator(new UniqueNameGenerator())
                .run(args);
    }

    /**
     * 由于需要混淆代码,混淆后类都是A B C,spring 默认是把A B C当成BeanName,BeanName又不能重复导致报错
     * 所以需要重新定义BeanName生成策略
     */
    @Component("UniqueNameGenerator")
    public static class UniqueNameGenerator extends AnnotationBeanNameGenerator
    {
        /**
         * 重写buildDefaultBeanName
         * 其他情况(如自定义BeanName)还是按原来的生成策略,只修改默认(非其他情况)生成的BeanName带上包名
         */
        @Override
        public @NotNull String buildDefaultBeanName(BeanDefinition definition)
        {
            //全限定类名
            return Objects.requireNonNull(definition.getBeanClassName());
        }
    }
}

包名保留

  • 考虑到 scanBasePackages 等特殊的注解配置,需要包名不被修改,配置如下:
-keeppackagenames com.langyastudio.edu
  • scanBasePackages 样例:

如果 com.langyastudio.edu 名称被混淆,将导致 scanBasePackages 失效

@SpringBootApplication(scanBasePackages = {"com.langyastudio.edu.*"})
public class Application
{
    public static void main(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.*
    
  • 配置类样例:

    public class WebLogAspect
    {
        /**
         * 包及其子包下所有类中的所有方法都应用切面里的通知
         */
        @Pointcut("execution(public * com.langyastudio.edu.*.controller..*.*(..))")
        public void webLog()
        {
        }
    
        @Before("webLog()")
        public void doBefore(JoinPoint joinPoint) throws Throwable
        {
        }
    }
    

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
@NoArgsConstructor
public class TokenVO implements Serializable
{
    /**
     * token
     */
    private String token;

    /**
     * token 前缀
     */
    private String tokenHead;
}

编译打包

打包成功一定要运行 编译后的 jar ,测试是否能否正常运行

mvnw clean package  -Dmaven.test.skip=true

image-20220327220710651

混淆效果

用于进一步查看打包后的 jar 文件是否符合要求,同时还可以辅助查找 jar 文件运行失败的原因

常见的反编译工具使用 jd-gui。下载地址:http://java-decompiler.github.io/

直接通过 jd-gui 窗口打开编译打包后的 jar 文件即可。

image-20220327221048292

参考文档

ProGuard manual

Code obfuscation for Spring Boot applications using the ProGuard plugin proguard-maven-plugin

springboot proguard 代码混淆

Spring Boot是一个开发框架,用于简化基于Java的应用程序的开发和部署。它提供了一个自动化配置的方式来简化开发过程,同时也提供了一些常用的功能模块,如安全、数据库连接等。 ProGuard是一个代码混淆工具,用于保护Java代码免受逆向工程和代码分析的攻击。它通过对代码进行优化和混淆,使得反编译后的代码难以理解和修改,从而增加了代码安全性。 Maven是一个项目管理工具,用于构建、发布和管理Java项目的依赖关系。它可以帮助我们方便地管理项目的依赖关系,包括第三方库和插件。同时,它还提供了一些命令和配置,来支持多模块的项目结构。 在使用Spring BootProGuardMaven实现代码混淆时,我们可以按照以下步骤进行操作: 1. 在Maven中创建一个多模块的项目结构。通过使用Maven的父子项目关系,我们可以在一个主项目中管理多个子模块。 2. 在子模块中引入Spring BootProGuard的依赖。在子模块的pom.xml文件中,添加对Spring BootProGuard的相关依赖配置。 3. 配置ProGuard混淆规则。在子模块中创建一个proguard.cfg文件,并添加相关的混淆规则。这些规则可以用于指定哪些类、方法和字段需要进行混淆,以及如何进行混淆。 4. 在Maven的构建过程中,添加对ProGuard的插件配置。通过对Maven的插件进行配置,使得在构建项目时自动应用ProGuard混淆规则。 5. 构建和执行项目。在Maven中使用命令进行项目的构建和执行,观察代码是否已经被混淆。如果一切顺利,你将会得到一个经过混淆代码。 综上所述,通过使用Spring BootProGuardMaven,我们可以实现对Java代码混淆保护。这种方式可以增加代码安全性,防止代码被逆向工程和代码分析的攻击。
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郎涯技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值