ProGuard加密混淆SpringBoot应用代码

news/2025/2/26 7:05:23

背景

我们的项目是基于SpringCloud架构的微服务应用,采用Docker离线部署方式交付客户,通过授权证书来控制应用的许可功能模块和使用时间。我们已经在代码层已经实现:

  • 基于多维度硬件指纹的绑定验证,cpu id、mac地址、磁盘序列、系统时钟、应用初始时间等
  • 双重时间验证机制(系统时间+硬件时钟)
  • 安全续期机制支持离线更新
  • 防调试/防篡改保护

来解决离线容器化部署Java应用程序授权问题。
整体流程如下:
授权实现流程图
该解决方案已基本能解决离线容器化部署Java应用程序授权问题,为了进一步加强安全防止通过反编译代码破解授权证书,我们决定对代码进行加密混淆。

Proguard

ProGuard 是一款开源 Java 类文件压缩器、优化器、混淆器和预验证器。因此,ProGuard 处理的应用程序和库更小、速度更快。

  • 缩减步骤检测并删除未使用的类、字段、方法和属性。
  • 优化器步骤优化字节码并删除未使用的指令。
  • 名称混淆步骤使用简短而无意义的名称重命名剩余的类、字段和方法。

Maven插件

我们的项目是SpringBoot 2.2.9 + jdk1.8,基于Maven构建,因此我们使用Proguard的Maven插件:proguard-maven-plugin来进行自动化代码混淆。下面是SpringBoot项目下基本的proguard-maven-plugin插件配置:

<build>
        <finalName>app</finalName>
        <plugins>
            <!-- 先编译 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version> <!-- 使用与你的 Maven 版本兼容的版本 -->
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.24</version> <!-- 使用与你的 Maven 版本兼容的版本 -->
                        </path>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>${org.mapstruct.version}</version>
                        </path>
                    </annotationProcessorPaths>
                    <compilerArgs>
                        <arg>-Aprojectlombok.classpath=${project.build.outputDirectory}</arg>
                    </compilerArgs>
                    <excludes>
                        <exclude>>com/hka/business/uaaserver/license/crypto/LicenseGenerator.java</exclude>
                    </excludes>
                </configuration>
            </plugin>
            <!-- 代码混淆编译配置: -->
            <plugin>
                <groupId>com.github.wvengen</groupId>
                <artifactId>proguard-maven-plugin</artifactId>
                <version>2.6.0</version>

                <executions>
                    <!-- 以下配置说明执行mvn的package命令时候,会执行proguard-->
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>proguard</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <proguardVersion>6.2.2</proguardVersion>
                    <!-- 就是输入Jar的名称,路径必须包含.jar-->
                    <injar>${project.build.finalName}.jar</injar>
                    <!-- 输出jar名称,路径必须包含.jar -->
                    <outjar>${project.build.finalName}.jar</outjar>
                    <!-- 是否混淆 默认是true -->
                    <obfuscate>true</obfuscate>
                    <!-- 引入外部配置proguard.cfg代替options标签 -->
                    <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>
                    <!-- 额外的jar包,通常是项目编译所需要的jar -->
                    <putLibraryJarsInTempDir>true</putLibraryJarsInTempDir>
                    <libs>
                        <lib>${java.home}/lib/rt.jar</lib>
                        <lib>${java.home}/lib/jce.jar</lib>
                        <lib>${java.home}/lib/jsse.jar</lib>
                    </libs>
                    <!-- 对输入jar进行过滤比如,如下配置就是对META-INFO文件不处理。 -->
                    <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter>
                    <!-- 输出路径配置,但是要注意这个路径必须要包括injar标签填写的jar,把jar包放到临时目录以便缩短命令行,解决windows的cmd有长度限制,出现CreateProcess error=206, 文件名或扩展名太长异常 -->
                    <outputDirectory>${project.basedir}/target</outputDirectory>
                    <!--options标签配置混淆的一些细节选项,比如哪些类不需要混淆,哪些需要混淆-->
                    <options>
                        <!-- proguard-maven-plugin支持通过option标签配置以及通过proguardInclude引入外部proguard.cfg配置来指定混淆规则,我们采用proguard.cfg方式,避免pom文件过长 -->
                    </options>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>net.sf.proguard</groupId>
                        <artifactId>proguard-base</artifactId>
                        <version>6.2.2</version>
                    </dependency>
                </dependencies>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot-dependencies.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

这里需要重点注意的是proguard-maven-plugin插件配置必须在Maven插件之后(先编译后混淆)。

proguard.cfg配置如下:

#指定Java的版本
-target 1.8
# 保留Spring Boot启动类
-keep class com.hka.business.uaaserver.UaaCenterApplication { *;}
-keepclassmembers class com.hka.business.uaaserver.UaaCenterApplication {
    @* *;
}

# 保留Spring相关注解
-keep @org.springframework.stereotype.Service class *
-keep @org.springframework.stereotype.Component class *
-keep @org.springframework.stereotype.Repository class *
-keep @org.springframework.stereotype.Controller class *
-keep @javax.annotation.PostConstruct class *
-keep @lombok.RequiredArgsConstructor class *
-keep @lombok.extern.slf4j.Slf4j class *
-keep @lombok.Data class *
-keep @lombok.AllArgsConstructor class *

# 保留MyBatis Mapper接口
-keep @org.apache.ibatis.annotations.Mapper class *
-keepclassmembers class * {
    @org.apache.ibatis.annotations.* *;
}

# 保留Nacos相关配置
-keep class com.alibaba.nacos.** { *; }

# 保留JAXB注解(Spring Boot可能需要)
-keepclassmembers class * {
    @javax.xml.bind.annotation.XmlElement *;
    @javax.xml.bind.annotation.XmlRootElement *;
}

# 保留包及其类上的注解
-keep class com.hka.business.uaaserver.message.**,com.hka.business.uaaserver.bi.** { *; }
-keepclassmembers class com.hka.business.uaaserver.message.**,com.hka.business.uaaserver.bi.** {
    @* *;
}

-keep class com.hka.business.uaaserver.application.** { *; }
-keepclassmembers class com.hka.business.uaaserver.application.** {
    @* *;
}

-keep class com.hka.business.uaaserver.config.** { *; }
-keepclassmembers class com.hka.business.uaaserver.config.** {
    @* *;
}

-keep class com.hka.business.uaaserver.infrastructure.** { *; }
-keepclassmembers class com.hka.business.uaaserver.infrastructure.** {
    @* *;
}

-keep class com.hka.business.uaaserver.interfaces.** { *; }
-keepclassmembers class com.hka.business.uaaserver.interfaces.** {
    @* *;
}

# 强制混淆的License包
-keep class !com.hka.business.uaaserver.license.** {
    *;
}

# 处理Lambda表达式
-keepclassmembers class * {
    private static synthetic java.lang.Object $deserializeLambda$(java.lang.invoke.SerializedLambda);
}

# 保留枚举类
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留序列化相关
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    static final java.io.ObjectStreamField[] serialPersistentFields;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 忽略 javax.activation 包中的类
-dontwarn javax.activation.**

# 忽略 javax.xml.bind 包中的类
-dontwarn javax.xml.bind.**

# 忽略 module-info 类
-dontwarn module-info

-ignorewarnings

-dontnote
# 配置保留注解
-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod

踩过的坑

需要完整配置需要保留的类

使用ProGuard最大的挑战应该是ProGuard默认会处理所有代码,因此需要精确配置哪些类需要保留,哪些需要混淆。特别是对于SpringBoot项目中存在大量注解、序列化、第三方框架、动态注入等场景。最简单的例子就是Spring Boot启动类的配置,不光要配置保留启动类同时还需要配置保留相关注解,否则混淆后的启动类class文件会没有注解。正常proguard.cfg配置片断如下:

# 保留Spring Boot启动类
-keep class com.hka.business.uaaserver.UaaCenterApplication { *;}
# 保留Spring Boot启动类注解
-keepclassmembers class com.hka.business.uaaserver.UaaCenterApplication {
    @* *;
}

对于其他普通的类也是一样,比如我们需要保留工程的bi模块不受代码混淆影响,也是需要同时配置相关类和注解保留配置,比如Lambda、Slf4j、mybatis以及spring注解等。proguard.cfg配置片断如下:

# 保留包及其类上的注解
-keep class com.hka.business.uaaserver.message.**,com.hka.business.uaaserver.bi.** { *; }
-keepclassmembers class com.hka.business.uaaserver.message.**,com.hka.business.uaaserver.bi.** {
    @* *;
}

Spring Bean 注入问题

在SpringBoot框架中,存在大量基于接口+依赖注入以及动态刷新机制来扩展第三方框架,例如集成SpringBoot Security Oauth2框架时,我们常会通过接口+依赖注入以及动态刷新机制来扩展ClientDetailsService,通过继承JdbcClientDetailsService ,扩展客户端加载机制,在使用数据库数据源基础增加redis缓存。但是我们在注入ClientDetailsService依赖时,无需显示指定注入RedisClientDetailsServiceImpl Bean。

java">@Slf4j
@Service
public class RedisClientDetailsServiceImpl extends JdbcClientDetailsService {
// 省略

public SecurityBrowserConfig(AuthenticationEntryPoint authenticationEntryPoint, CustomAccessDeniedHandler customAccessDeniedHandler, TokenStore tokenStore, UserDetailsService userDetailsService, RedisClientDetailsServiceImpl clientDetailsService) {
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.customAccessDeniedHandler = customAccessDeniedHandler;
        this.tokenStore = tokenStore;
        this.userDetailsService = userDetailsService;
        // 这里仅需要通过接口方式动态注入Bean依赖
        this.clientDetailsService = clientDetailsService;
    }

但是通过代码混淆后,无法正常启动服务,出现异常提示如下:

2025-02-20 10:58:03.930 WARN [main]org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext.refresh:559 -Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userApiController' defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/hka/business/uaaserver/interfaces/web/api/UserApiController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userApplicationImpl' defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/hka/business/uaaserver/application/impl/UserApplicationImpl.class]: Unsatisfied dependency expressed through constructor parameter 2; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityBrowserConfig' defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/hka/business/uaaserver/config/SecurityBrowserConfig.class]: Unsatisfied dependency expressed through constructor parameter 4; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.provider.ClientDetailsService' available: expected single matching bean but found 2: redisClientDetailsServiceImpl,clientDetailsService

这里问题暂时没通过直接修改项目的proguard.cfg配置解决(目前网上暂时没有相关直接的解决方案),而是通过曲线救国方式解决。主要是将项目的授权逻辑剥离封装成独立的纯Java依赖项目,在构建依赖项目时进行代码混淆,避免代码混淆影响Spring Bean依赖关系注入。实际的项目通过私有仓库引入混淆后的依赖包达到代码混淆的目的。

具体实施步骤:

  • 剥离授权逻辑,抽象成纯Java项目,没有任何Bean依赖和注解。
  • 依赖项目集成proguard-maven-plugin插件,支持在推送依赖到私有仓库时进行代码混淆,具体就是在执行mvn:deploy命令触发代码混淆。
  • 推送依赖到私有仓库
  • 应用项目引入授权依赖

下面是依赖项目的proguard-maven-plugin插件配置:
基本和上文的配置一致,只是多了deploy触发proguard的配置。

 <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.3</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <!-- 配置 maven-deploy-plugin 排除 sources 文件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-deploy-plugin</artifactId>
                <version>2.8.2</version>
                <configuration>
                    <skip>false</skip>
                    <file>${project.build.directory}/${project.build.finalName}.jar</file> <!-- 禁用附加文件 -->
                </configuration>
            </plugin>
            <!-- 代码混淆编译配置: -->
            <plugin>
                <groupId>com.github.wvengen</groupId>
                <artifactId>proguard-maven-plugin</artifactId>
                <version>2.6.0</version>
                <executions>
                    <!-- 以下配置说明执行mvn的package命令时候,会执行proguard-->
                    <execution>
                        <id>package-proguard</id>
                        <phase>package</phase>
                        <goals>
                            <goal>proguard</goal>
                        </goals>
                    </execution>
                    <!-- 以下配置说明执行mvn的deploy命令时候,会执行proguard-->
                    <execution>
                        <id>deploy-proguard</id>
                        <phase>deploy</phase>
                        <goals>
                            <goal>proguard</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <proguardVersion>6.2.2</proguardVersion>
                    <injar>${project.build.finalName}.jar</injar>
                    <outjar>${project.build.finalName}.jar</outjar>
                    <obfuscate>true</obfuscate>
                    <proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>
                    <putLibraryJarsInTempDir>true</putLibraryJarsInTempDir>
                    <libs>
                        <lib>${java.home}/lib/rt.jar</lib>
                        <lib>${java.home}/lib/jce.jar</lib>
                        <lib>${java.home}/lib/jsse.jar</lib>
                    </libs>
                    <inLibsFilter>!META-INF/**,!META-INF/versions/9/**.class</inLibsFilter>
                    <outputDirectory>${project.basedir}/target</outputDirectory>
                    <options>
                    </options>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>net.sf.proguard</groupId>
                        <artifactId>proguard-base</artifactId>
                        <version>6.2.2</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>

proguard.cfg配置如下:
纯Java依赖项目的proguard.cfg配置就非常简单,仅需要保留保留所有公共类和方法配置,其他基本可以全部使用插件的默认配置。

# 指定Java的版本
-target 1.8

# 保留所有公共类和方法
-keep public class * {
    public *;
}
# 忽略 javax.activation 包中的类
-dontwarn javax.activation.**

# 忽略 javax.xml.bind 包中的类
-dontwarn javax.xml.bind.**

# 忽略 module-info 类
-dontwarn module-info

-ignorewarnings

-dontnote

以上就是我们使用proguard代码混淆的分享。也希望有大佬看到我的帖子可以帮忙分享Spring Bean 注入问题的解决方案。

参考

A慧眼如炬-ProGuard加密混淆Java代码
proguard


http://www.niftyadmin.cn/n/5868273.html

相关文章

绕过information_schema与order by注入以及seacsmv9注入

一:information_schema绕过 1,、sys数据库包含了许多视图&#xff0c;这些视图整合了来自information_schema和performance_schema的数据&#xff0c;攻击者可以利用这些视图来获取数据库结构信息。 -- 获取所有数据库名 SELECT DISTINCT table_schema FROM sys.schema_table_…

大数据与Hadoop综合解析

一、大数据概述 在数字化转型的浪潮中&#xff0c;大数据已成为不可或缺的资源。它不仅改变了企业的运营方式&#xff0c;还重塑了整个行业格局。大数据主要应对海量数据的采集、存储与分析计算挑战&#xff0c;帮助企业从数据中提取价值&#xff0c;驱动决策和创新。 数据单位…

Oracle 数据库基础入门(一):搭建数据管理基石

在当今数字化时代&#xff0c;数据库作为数据管理的核心工具&#xff0c;对于各类应用系统的开发至关重要。尤其是在 Java 全栈开发领域&#xff0c;掌握一款强大的数据库技术是必备技能。Oracle 数据库以其卓越的性能、高度的可靠性和丰富的功能&#xff0c;在企业级应用中广泛…

DeepSeek开源周 Day02:从DeepEP开源趋势重新审视大模型Infra

DeepEP 今天DeepSeek开源周第二天&#xff0c;开放了DeepEP仓库&#xff0c;属实看了下源码&#xff0c;和昨天FlashMLA一样&#xff0c;C权重&#xff08;包括CUDA&#xff09;还是占据了绝对部分&#xff0c;作为调包侠的我&#xff0c;看到之后望而却步&#xff0c;想看原理…

欧拉回路与哈密尔顿回路: Fleury算法与Hierholzer 算法(C++)

图论中的回路是指一个路径, 它从某个顶点开始, 经过所有边恰好一次, 并回到起始顶点. 定义 欧拉回路: 从一个顶点出发, 经过每条边恰好一次, 并且最终回到起始顶点. 哈密尔顿回路: 从一个顶点出发, 经过每个顶点恰好一次, 并且最终回到起始顶点. 欧拉路径: 从一个顶点出发, …

[实现Rpc] 测试 | rpc部分功能联调 | debug | 理解bind

目录 服务端 客户端 Debug 运行 总结 服务端 调用 on Request 对请求做出回应 on 对...做处理 #include "../../common/net.hpp" #include "../../common/message.hpp" #include "../../common/dispatcher.hpp" #include "../../se…

Node.js 内置模块简介(带示例)

目录 1. fs&#xff08;文件系统&#xff09;模块 2. http 模块 3. path 模块 4. os 模块 5. events 模块 6. crypto 模块 1. fs&#xff08;文件系统&#xff09;模块 fs 模块提供了与文件系统进行交互的功能&#xff0c;包括文件的读写、删除、重命名等操作。它有同步…

安装VM和Centos

安装VM 一、打开虚拟机 二、选择典型 三、选择光盘 四、指定虚拟机位置 五、设置磁盘大小并拆分为多个文件 六、完成 安装Centos 一、上述过程完成后我们直接打开虚拟机 二、语言选择中文 三、默认安装位置并点击完成 四、点击开始安装 五、点击设置密码 设置完密码后点击完成…