本文主要讲解如何支持多重jar包解密,即spring boot包也进行一层加密?如需了解依赖加密后的SDK包后,spring boot fatjar启动运行报NoClassDefFoundError参见《Classfinal问题处理及改造升级(一)》
发现问题
同样使用classfinal将spring boot包进行加密
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zeeeeeey.jm</groupId>
<artifactId>jm-user-service</artifactId>
<name>jm-user-service</name>
<version>1.0.0</version>
<description>Demo project for Spring Boot</description>
<dependencies>
<!-- 该SDK为加密包-->
<dependency>
<groupId>com.zeeeeeey.jm</groupId>
<artifactId>jm-user-sdk</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.3.7.RELEASE</version>
<configuration>
<mainClass>com.zeeeeeey.jm.JmUserServiceApplication</mainClass>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<!-- https://gitee.com/roseboy/classfinal -->
<groupId>net.roseboy</groupId>
<artifactId>classfinal-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<password>000000</password><!--加密打包之后pom.xml会被删除,不用担心在jar包里找到此密码-->
<packages>com.zeeeeeey.jm</packages>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>classFinal</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
启动打包后的fatjar
java -javaagent:zeeeeey-agent.jar="-pwd 000000" -jar zeeeeey-jm-service-1.0.0.jar
当代码执行到调用SDK代码块时出现异常信息,带返回值方法返回null
public String getName() {
return null;
}
显然SDK内部的class文件没有解密成功
问题处理
通过排查源码,可以发现当读取被加密Jar包时,无法正常读取。
/**
* 在jar文件或目录中读取文件字节
*
* @param workDir jar文件或目录
* @param name 文件名
* @return 文件字节数组
*/
public static byte[] readEncryptedFile(File workDir, String name) {
byte[] bytes = null;
String fileName = ENCRYPT_PATH + name;
//这里有点问题,当workDir为嵌套jar是,该方式是无法读取到文件的。
//workDir的Path是:XXXX/zeeeeey-jm-service-1.0.0.jar!/zeeeeey-jm-sdk-1.0.0.jar
if (workDir.isFile()) {
bytes = JarUtils.getFileFromJar(workDir, fileName);
} else {//war解压的目录
File file = new File(workDir, fileName);
if (file.exists()) {
bytes = IoUtils.readFileToByte(file);
}
}
return bytes;
}
可以按照以下方式修改:
public static byte[] readEncryptedFile(File workDir, String name) {
byte[] bytes = null;
String fileName = ENCRYPT_PATH + name;
//jar文件
if (workDir.getPath().endsWith(".jar")) {
bytes = JarUtils.getFileFromJar(workDir, fileName);
} else {//war解压的目录
File file = new File(workDir, fileName);
if (file.exists()) {
bytes = IoUtils.readFileToByte(file);
}
}
return bytes;
}
按照如下方法修改后,发现同样无法进行解密,我们进去JarUtils.getFileFormJar方法看下
/**
* 在压缩文件中获取一个文件的字节
*
* @param zip 压缩文件
* @param fileName 文件名
* @return 文件的字节
*/
public static byte[] getFileFromJar(File zip, String fileName) {
ZipFile zipFile = null;
try {
if (!zip.exists()) {
return null;
}
zipFile = new ZipFile(zip);
ZipEntry zipEntry = zipFile.getEntry(fileName);
if (zipEntry == null) {
return null;
}
InputStream is = zipFile.getInputStream(zipEntry);
return IoUtils.toBytes(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
IoUtils.close(zipFile);
}
return null;
}
可以发现,该方式只解析了一层jar包,自然无法获取jar in jar里面的内容。那么如何支持读取jar in jar里的内容呢,一种是直接嵌套读取下一层jar包,但该种方式似乎不太优雅,我们可以使用URL协议方式获取资源,因为springboot支持了URL进行嵌套jar资源的类加载。修改代码如下:
public static byte[] getFileFromJar(File file, String fileName) {
InputStream inputStream = null;
try {
URL url = new URL("jar:" + file.toURI().toURL() + "!/" + fileName);
inputStream = url.openStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
IoUtils.copy(inputStream, byteArrayOutputStream);
return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
} finally {
if (Objects.nonNull(inputStream)) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
如果想要了解Spring boot如何改造URL协议去支持嵌套Jar资源读取的,可以去看下Spring-boot-loader项目源码。