ProGuard加密混淆SpringBoot应用代码

06-01 1263阅读

背景

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

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

    来解决离线容器化部署Java应用程序授权问题。

    整体流程如下:

    ProGuard加密混淆SpringBoot应用代码

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

    Proguard

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

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

      Maven插件

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

              app
              
                  
                  
                      org.apache.maven.plugins
                      maven-compiler-plugin
                      3.8.1 
                      
                          1.8
                          1.8
                          
                              
                                  org.projectlombok
                                  lombok
                                  1.18.24 
                              
                              
                                  org.mapstruct
                                  mapstruct-processor
                                  ${org.mapstruct.version}
                              
                          
                          
                              -Aprojectlombok.classpath=${project.build.outputDirectory}
                          
                          
                              >com/hka/business/uaaserver/license/crypto/LicenseGenerator.java
                          
                      
                  
                  
                  
                      com.github.wvengen
                      proguard-maven-plugin
                      2.6.0
                      
                          
                          
                              package
                              
                                  proguard
                              
                          
                      
                      
                          6.2.2
                          
                          ${project.build.finalName}.jar
                          
                          ${project.build.finalName}.jar
                          
                          true
                          
                          ${project.basedir}/proguard.cfg
                          
                          true
                          
                              ${java.home}/lib/rt.jar
                              ${java.home}/lib/jce.jar
                              ${java.home}/lib/jsse.jar
                          
                          
                          !META-INF/**,!META-INF/versions/9/**.class
                          
                          ${project.basedir}/target
                          
                          
                              
                          
                      
                      
                          
                              net.sf.proguard
                              proguard-base
                              6.2.2
                          
                      
                  
                  
                      org.springframework.boot
                      spring-boot-maven-plugin
                      ${spring-boot-dependencies.version}
                      
                          
                              
                                  repackage
                              
                          
                      
                  
              
          
      

      这里需要重点注意的是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。

      @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的配置。

         
                
                    
                        org.apache.maven.plugins
                        maven-compiler-plugin
                        3.3
                        
                            1.8
                            1.8
                        
                    
                    
                    
                        org.apache.maven.plugins
                        maven-deploy-plugin
                        2.8.2
                        
                            false
                            ${project.build.directory}/${project.build.finalName}.jar 
                        
                    
                    
                    
                        com.github.wvengen
                        proguard-maven-plugin
                        2.6.0
                        
                            
                            
                                package-proguard
                                package
                                
                                    proguard
                                
                            
                            
                            
                                deploy-proguard
                                deploy
                                
                                    proguard
                                
                            
                        
                        
                            6.2.2
                            ${project.build.finalName}.jar
                            ${project.build.finalName}.jar
                            true
                            ${project.basedir}/proguard.cfg
                            true
                            
                                ${java.home}/lib/rt.jar
                                ${java.home}/lib/jce.jar
                                ${java.home}/lib/jsse.jar
                            
                            !META-INF/**,!META-INF/versions/9/**.class
                            ${project.basedir}/target
                            
                            
                        
                        
                            
                                net.sf.proguard
                                proguard-base
                                6.2.2
                            
                        
                    
                
            
        

        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

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码