打包Java项目不借助工具_Java 将项目打包成Jar,并在Jar中使用来自外部的第三方Jar包,而不是直接依赖(bcprov-jdk15on)...

一. 背景

最近需要将一项加解密功能从Web应用中剥离,制作成一个独立可执行的Jar包,供客户离线使用。加解密时使用到了bcprov轻量级加密API,这个Jar包在运行时会检索签名,比对自身包含的文件大小,若有任何一项出现异常,则运行时直接报错:

java.lang.SecurityException: JCE cannot authenticate the provider BC

由于我使用的maven打包插件是maven-shade-plugin,在设置了createDependencyReducedPom参数值为false后,为了避免重复依赖已有模块,会对第三方依赖解压再压缩,这个过程会导致META-INF中的5份文件的大小发生变化。所以我想到不能直接通过Maven pom.xml进行依赖,而是在程序运行时动态的将需要的Jar(bcprov-jdk15on.jar)通过类加载器URLClassLoader加载至JVM中。但是问题接踵而来,第三方Jar(bcprov-jdk15on.jar)存放在哪里?如何获取URLClassLoader加载Jar时需要的URL?

针对第一个问题,我决定将第三方Jar放在Resource目录下,这是因为不能向客户暴露过多有关加解密的实现细节,并且也方便客户使用(如若不然,就需要提供项目Jar和第三方加解密Jar两个Jar包了)。

针对第二个问题,由于Resource目录内的资源在编译后存放在Classpath中,我曾尝试使用ClassLoader getResourceAsStream(String fileName)的方式获取Jar的Inputstream,将其输出到物理磁盘上任意位置(生成一份新的Jar),最后通过file.toURI().toURL()来获取URL。遗憾的是,最终读取新的Jar的META-INF目录中,MANIFEST.MF文件的CRC SHA发生了改变,导致加解密时运行时报错:

java.lang.SecurityException: Invalid signature file digest for Manifest main attributes

无效的数字签名。

因此,我最终的做法是:在项目运行时,直接将携带第三方Jar的完整项目Jar包进行解压缩,这样得到的第三方Jar没有任何损坏。

二. 实现方式

1. 构建Jar加载器

//Jar加载器

JarLoader jarLoader = new JarLoader((URLClassLoader) ClassLoader.getSystemClassLoader());

2. 临时目录,解压缩的文件将被暂时放置在临时目录内 为了尽可能的避免用户看到加解密细节,我将bc解压在用户的临时目录下。

String systemTempPath = System.getProperties().getProperty("java.io.tmpdir");

3. 既然想解压项目完整Jar,那么首先应该定位到这个Jar。我的做法是以当前文件为基准点来进行定位,通过getProtectionDomain().getCodeSource().getLocation().getPath()来获取jar包的绝对路径(ps: 不要直接获取当前文件的路径,因因为当前的class文件封装在Jar包内,路径是类似jar:file:/.../.../xxx.jar!/com.c1的相对路径,而不是file://C://xxx.jar 这种绝对路径)。

注意: 一定要在加解密操作之前完成第三方jar对jvm的注入工作,否则在加密接时会报错 依赖的类(我这里是bc)找不到。

try {

//准备工作 将bcprov-jdk15on-1.57.jar加载至jvm中

try {

//当前文件所在jar包的物理路径 LocalDecrypt.class 我这份java文件的类名

String currentJarPath = LocalDecrypt.class.getProtectionDomain().getCodeSource().getLocation().getPath();

//当前文件所在目录的物理路径

String parentPath = new File(currentJarPath).getParentFile().getPath();

String unZipInput = parentPath.concat(File.separator).concat("项目Jar包的完整名称");

String unZipOutput = systemTempPath.concat(File.separator);

unZip(new File(unZipInput), new File(unZipOutput));

// 加载Jar

JarLoader.loadjar(jarLoader, systemTempPath, "bcprov-jdk15on-1.57.jar");

} catch (Exception e) {

throw new Exception("jarLoader加载时出现异常: " + e.toString());

}

for (String cipher : ciphers) {

String decrypt = null;

String errorMsg = null;

for (String privateKey : privateKeys) {

try {

// 密码解密

decrypt = DaemonSupport.decrypt(cipher, privateKey);

} catch (Exception e) {

errorMsg = e.toString();

//System.out.println(e.toString());

}

}

if (null == decrypt) {

System.out.println(String.format("密文: %s, 私钥: %s 解密时出错,原因: %s", cipher, privateKeys.toString(), errorMsg));

}

cleartextMap.put(cipher, decrypt);

}

return cleartextMap;

} finally {

File file = new File(systemTempPath.concat(File.separator).concat("bcprov-jdk15on-1.57.jar"));

if (file.exists()) {

file.delete();

}

}

4. Jar包装载器

public class JarLoader {

private URLClassLoader urlClassLoader;

public JarLoader(URLClassLoader urlClassLoader) {

this.urlClassLoader = urlClassLoader;

}

public void loadJar(URL url) throws Exception {

Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);

addURL.setAccessible(true);

addURL.invoke(urlClassLoader, url);

}

public static void loadjar(JarLoader jarLoader, String path, String targetFileName) throws Exception {

File libdir = new File(path);

if (libdir != null && libdir.isDirectory()) {

// 对目录下的文件进行过滤,只保留后缀为.jar的文件

File[] listFiles = libdir.listFiles(file -> {

return file.exists() && file.isFile() && file.getName().endsWith(".jar");

});

for (File file : listFiles) {

if(file.getName().equals(targetFileName)) {

jarLoader.loadJar(file.toURI().toURL());

}

}

} else {

throw new Exception("目标Jar包路径不存在");

}

}

}

注:下文的 *** 代表文件名的版本号。 # 【bcprov-jdk15on-***.jar文文档.zip】 包含: 文文档:【bcprov-jdk15on-***-javadoc-API文档-文(简体)版.zip】 jar包下载地址:【bcprov-jdk15on-***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【bcprov-jdk15on-***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【bcprov-jdk15on-***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【bcprov-jdk15on-***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: bcprov-jdk15on-***.jar文文档.zip,java,bcprov-jdk15on-***.jar,org.bouncycastle,bcprov-jdk15on,***,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,bouncycastle,bcprov,jdk15on,文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【bcprov-jdk15on-***.jar文文档.zip】,再解压其的 【bcprov-jdk15on-***-javadoc-API文档-文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件; # Maven依赖: ``` <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>***</version> </dependency> ``` # Gradle依赖: ``` Gradle: implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '***' Gradle (Short): implementation 'org.bouncycastle:bcprov-jdk15on:***' Gradle (Kotlin): implementation("org.bouncycastle:bcprov-jdk15on:***") ``` # 含有的 Java package(包)(此处仅列举3个): ``` org.bouncycastle org.bouncycastle.asn1 org.bouncycastle.asn1.anssi ...... ``` # 含有的 Java class(类)(此处仅列举3个): ``` org.bouncycastle.LICENSE org.bouncycastle.asn1.ASN1ApplicationSpecific org.bouncycastle.asn1.ASN1ApplicationSpecificParser ...... ```
注:下文的 *** 代表文件名的版本号。 # 【bcprov-jdk15on-***.jar文文档.zip】 包含: 文文档:【bcprov-jdk15on-***-javadoc-API文档-文(简体)版.zip】 jar包下载地址:【bcprov-jdk15on-***.jar下载地址(官方地址+国内镜像地址).txt】 Maven依赖:【bcprov-jdk15on-***.jar Maven依赖信息(可用于项目pom.xml).txt】 Gradle依赖:【bcprov-jdk15on-***.jar Gradle依赖信息(可用于项目build.gradle).txt】 源代码下载地址:【bcprov-jdk15on-***-sources.jar下载地址(官方地址+国内镜像地址).txt】 # 本文件关键字: bcprov-jdk15on-***.jar文文档.zip,java,bcprov-jdk15on-***.jar,org.bouncycastle,bcprov-jdk15on,***,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,bouncycastle,bcprov,jdk15on,文API文档,手册,开发手册,使用手册,参考手册 # 使用方法: 解压 【bcprov-jdk15on-***.jar文文档.zip】,再解压其的 【bcprov-jdk15on-***-javadoc-API文档-文(简体)版.zip】,双击 【index.html】 文件,即可用浏览器打开、进行查看。 # 特殊说明: ·本文档为人性化翻译,精心制作,请放心使用。 ·只翻译了该翻译的内容,如:注释、说明、描述、用法讲解 等; ·不该翻译的内容保持原样,如:类名、方法名、包名、类型、关键字、代码 等。 # 温馨提示: (1)为了防止解压后路径太长导致浏览器无法打开,推荐在解压时选择“解压到当前文件夹”(放心,自带文件夹,文件不会散落一地); (2)有时,一套Java组件会有多个jar,所以在下载前,请仔细阅读本篇描述,以确保这就是你需要的文件; # Maven依赖: ``` <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>***</version> </dependency> ``` # Gradle依赖: ``` Gradle: implementation group: 'org.bouncycastle', name: 'bcprov-jdk15on', version: '***' Gradle (Short): implementation 'org.bouncycastle:bcprov-jdk15on:***' Gradle (Kotlin): implementation("org.bouncycastle:bcprov-jdk15on:***") ``` # 含有的 Java package(包)(此处仅列举3个): ``` org.bouncycastle org.bouncycastle.asn1 org.bouncycastle.asn1.anssi ...... ``` # 含有的 Java class(类)(此处仅列举3个): ``` org.bouncycastle.LICENSE org.bouncycastle.asn1.ASN1ApplicationSpecific org.bouncycastle.asn1.ASN1ApplicationSpecificParser ...... ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值