最新版SpringBoot結合ProGuard實現程式碼混淆

2020-09-30 16:00:58

參考案例

1、Springboot+proguard+maven 混淆.
2、proguard-spring-boot-example
3、官方解釋
4、Proguard的Keep使用方法
5、ProGuard 最全混淆規則說明
6、ProGuard程式碼混淆技術詳解
7、使用proguard混淆springboot程式碼

前言

研究ProGuard也花了兩天時間,其實最主要的時間花在前面proguard讀取jar包的時候相關jar衝突的問題,但是總的來說不用拆分SpringBoot專案並且實現程式碼混淆已經很舒服了。

ProGuard整合

1.maven的設定

具體設定如下:

<build>
		<finalName>${artifactId}</finalName>
		<plugins>
			<plugin>
				<groupId>com.github.wvengen</groupId>
				<artifactId>proguard-maven-plugin</artifactId>
				<executions>
					<execution>
						<phase>package</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>
					<!--<proguardInclude>${project.basedir}/proguard.cfg</proguardInclude>-->
					<obfuscate>true</obfuscate>
					<options>
						<!-- 不做收縮(刪除註釋、未被參照程式碼)-->
						<option>-dontshrink</option>
						<!-- 不做優化(變更程式碼實現邏輯)-->
						<option>-dontoptimize</option>
						<!--保持目錄結構,否則spring的自動注入無法使用-->
						<!--<option>-keepdirectories</option>-->
						<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,
							SourceFile,LineNumberTable, *Annotation*,EnclosingMethod
						</option>
						<option>-adaptclassstrings</option>
						<option>
							<!-- 保護程式入口 -->
							 -keep class com.jingchen.ccny.CmepApplication { *; }
						</option>
						<option>-keepnames interface ** { *; }</option>
						<!-- 固定幾個類不能混淆-->
						<option>-keepnames class com.jingchen.ccny.base.BaseService { *; }</option>
						<option>-keep class com.jingchen.ccny.common.cache.ConvertorNewCache { *; }</option>
						<option>-keep class com.jingchen.ccny.base.ControllerContext { *; }</option>
						<option>-keep class * extends com.jingchen.ccny.base.BaseService</option>
						<option>-keep class * implements com.jingchen.ccny.common.service.CallBackGuiService</option>
						<option>-keep class * implements com.jingchen.ccny.common.service.CallBackUDService</option>
						<option>-keep class com.jingchen.ccny.util.SpringUtil</option>
						<!--<option>-keep interface * extends * { *; }</option>-->
						<!-- 此選項將在所有包的所有類中儲存所有原始定義的註釋.-->
						<option>
							    -keep class * {
							@org.springframework.beans.factory.annotation.Autowired *;
							@org.springframework.beans.factory.annotation.Value *;
							@org.springframework.stereotype.Service *;
							@org.springframework.stereotype.Component *;
							@org.springframework.scheduling.annotation.Scheduled *;

							}
						</option>
					</options>
					<libs>
						<!-- Include main JAVA library required.-->
						<lib>${java.home}/lib/rt.jar</lib>
						<lib>${java.home}/lib/jce.jar</lib>
					</libs>
				</configuration>
				<dependencies>
					<dependency>
						<groupId>net.sf.proguard</groupId>
						<artifactId>proguard-base</artifactId>
						<version>6.2.2</version>
					</dependency>
				</dependencies>
			</plugin>

			<!-- Maven assembly must be run after proguard obfuscation so it take already obfuscated files.-->
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
				<executions>
					<execution>
						<goals>
							<goal>repackage</goal>
						</goals>
						<configuration>
							<mainClass>com.jingchen.ccny.CcnyApplication</mainClass>
						</configuration>
					</execution>
				</executions>
			</plugin>
		</plugins>
	</build>

這裡裡面級聯參照的jar很多,建議設定了私服的人先把映象地址設定成maven中央倉庫地址,這樣先下下來相關依賴的包,然後再上傳到你們的私服上去。據我研究中知道的jar就有:
在這裡插入圖片描述

注意:不連外網設定maven中央倉庫的話,少了jar你們會很頭疼的。而且不止net.sf.proguard相關包,還包括了com.guardsquare.proguard-base 和 com.guardsquare.proguard-core 相關的jar,所以真的連外網下包很重要!!!!

推薦一個IDEA解決maven參照衝突的外掛:Maven Helper

2.相關異常解決

  • idea A required class was missing … org/apache/tools/ant/BuildListener
    問題原因
    這個問題就是上面提到的因為apache的編譯用了一個ant-1.9.3的包,這個是級聯參照的,開始我是內網maven私服,單純的引入net.sf.proguard相關和com.github.wvengen相關的jar還是會缺少很多jar
    解決方案
    連上外網,設定你的maven的setting.xml 的mirror映象地址,設定成Maven中央倉庫的地址,將相關的jar都下下來,然後再通過命令把你本地maven倉庫的jar上傳到私服去
  • Can’t process class [META-INF/versions/9/org/apache/logging/log4j/util/Base64Util.class]
  • Can’t process class [META-INF/versions/11/module-info.class]
    問題原因
    這個問題的原因有很多方面,最主要的就是我們的jdk版本是1.8,我最開始用的ProGuard是5.3.3版本,然而我們SpringBoot的版本是2.3.3版本,SpringBoot2.3.3版本太新了,裡面參照的相關包都是java9和java11的版本,這樣ProGuard在讀jar的時候會無法識別。這些問題在提升Proguard版本到6.2.2之後都解決了
    解決方案
    開始我的解決方案是忽略這些相關的jar, 例如在pom.xml的option設定:
<option>-libraryjars  ${settings.localRepository}/com/zaxxer/HikariCP/3.4.5/HikariCP-3.4.5.jar(META-INF/versions/11/module-info.class)</option>

但是我這樣設定之後,重新打包會提示:

  • The same input jar [E:\maven\repo\com\zaxxer\HikariCP\3.4.5\HikariCP-3.4.5.jar] is specified twice.
    解決方案參考
    而且我還嘗試了maven參照的時候排除這些高版本的級聯參照jar,單獨參照低版本,但最終還是因為太繁瑣而放棄了。直接提升Proguard版本到6.2.2 這些讀jar的版本問題就解決了。
  • Annotation-specified bean name ‘a’ for bean class
    問題原因
    出現這個問題主要還是混淆之後,bean重名了,spring預設是把類名的首字母小寫載入到容器裡面,我們混淆類名之後,就容易造成beanName重複。
    解決方案
    慶幸的是,我們可以通過改變spring載入bean的命名策略來解決這個問題,把包名帶上,同時在獲取Spring上下文getBean的時候,加上包名路徑即可
    啟動類設定,具體如下:
@SpringBootApplication
public class CcnyApplication{

	public static class CustomGenerator implements BeanNameGenerator {

		@Override
		public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
			return definition.getBeanClassName();
		}
	}

	public static void main(String[] args) {
		SpringApplicationBuilder sab=new SpringApplicationBuilder(CcnyApplication.class)
				.beanNameGenerator(new CustomGenerator());
		//這裡如果想列印你載入的Spring的bean,可以這樣做:
		ApplicationContext ac =sab.run(args);
		Arrays.stream(ac.getBeanDefinitionNames()).forEach(System.out::println);
	}
}

這樣設定,你啟動的時候就能看到載入的所有的beanName(這裡Service會帶上package路徑)

其他地方getBean的用法:

//這裡的packagePath = com.jingchen.ccny.service
CallBackGuiService callBackGuiService = (CallBackGuiService) SpringUtil.getBean(packagePath+serviceName);
                    callBackResult = callBackGuiService.excute(convertMap);

這樣你就能正常的獲取到Spring容器載入的beanName了

注意事項

  • 基本上影響打包和啟動的就上面一些問題了,其他的就是你們專案裡面的細節了,
  • 比如DAO要保留,要和mybatis裡面的Mapper對映對應,DAO裡面的方法傳參要改為map或者實體,另外序列化後的實體要保留
  • Controller裡面的方法入參,如果用了實體,這部分實體也要保留(保證其變數不會被混淆,不然傳值收不到)
  • 另外就是你們spring相關的XML裡面,如果單獨設定了Bean和Bean屬性的,這類bean要保留,不能被混淆
  • 我這裡保留了所有的介面和介面裡面的方法,已經我們自定義的抽象類BaseService裡面的方法名不會被混淆,這些你們可以自己定義,而且我這裡定義了有標註@Component的類也保留類名,按照我上面的設定,基本上可以不用重新構建beanName。正常的application啟動就完事了
  • 其他的沒了,就看你們還有沒有什麼特定的類不能被混淆,以及你們要混淆的力度(我們的要求是保留所有類名、介面資訊和抽象類資訊,除此之外的所有類和方法都被混淆!)

總的來說花了兩天時間,有這樣的成果也是值得高興的,前一天解決jar衝突的比較多,主要原因就是最開始XX架構師搭建這個專案採用最新的SpringBoot版本,jdk確是1.8 , 很多不相容。

來個最終的效果圖吧:
在這裡插入圖片描述