Java将项⽬打包成Jar,并在Jar中使⽤来⾃外部的第三⽅Jar
包,⽽不是直接依赖(bc。。。
⼀. 背景
最近需要将⼀项加解密功能从Web应⽤中剥离,制作成⼀个独⽴可执⾏的Jar包,供客户离线使⽤。加解密时使⽤到了bcprov轻量级加密API,这个Jar包在运⾏时会检索签名,⽐对⾃⾝包含的⽂件⼤⼩,若有任何⼀项出现异常,则运⾏时直接报错:
java.lang.SecurityException: JCE cannot authenticate the provider BC
由于我使⽤的maven打包插件是maven-shade-plugin,在设置了createDependencyReducedPom参数值为false后,为了避免重复依赖已有模块,会对第三⽅依赖解压再压缩,这个过程会导致META-INF中的5份⽂件的⼤⼩发⽣变化。所以我想到不能直接通过l进⾏依赖,⽽是在程序运⾏时动态的将需要的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),最后通过URI().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) SystemClassLoader());
2. 临时⽬录,解压缩的⽂件将被暂时放置在临时⽬录内 为了尽可能的避免⽤户看到加解密细节,我将bc解压在⽤户的临时⽬录下。String systemTempPath = Properties().getProperty("pdir");
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 = ProtectionDomain().getCodeSource().getLocation().getPath();
//当前⽂件所在⽬录的物理路径
String parentPath = new File(currentJarPath).getParentFile().getPath();
String unZipInput = at(File.separator).concat("项⽬Jar包的完整名称");
String unZipOutput = at(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.String());
}
}
if (null == decrypt) {
System.out.println(String.format("密⽂: %s, 私钥: %s 解密时出错,原因: %s", cipher, String(), errorMsg)); }
cleartextMap.put(cipher, decrypt);
}
return cleartextMap;
} finally {
File file = new at(File.separator).concat("bcprov-jdk15on-1.57.jar"));
if (ists()) {
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 = DeclaredMethod("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 -> {
ists() && file.isFile() && Name().endsWith(".jar");
});
for (File file : listFiles) {
Name().equals(targetFileName)) {
jarLoader.URI().toURL());
}
}
} else {
throw new Exception("⽬标Jar包路径不存在");
}
}
}
5. 解压压缩包到指定⽬录
private static void unZip(File inputFile, File outputFile) {
if (!ists()) {
outputFile.mkdirs();
}
ZipFile zipFile;
ZipInputStream zipInput = null;
OutputStream output = null;
InputStream input;
File file;
try {
zipFile = new ZipFile(inputFile);
zipInput = new ZipInputStream(new FileInputStream(inputFile));
String path = AbsolutePath() + File.separator;
for (Enumeration entries = ies(); entries.hasMoreElements(); ) { java.util.zip.ZipEntry entry = (java.util.zip.ZipEntry) Element(); // 从压缩⽂件⾥获取指定已压缩⽂件的输⼊流
input = InputStream(entry);
if (!Name().equals("bcprov-jdk15on-1.57.jar")) {
continue;maven打包本地jar包
}
String outPath = (path + File.separator + Name());
file = new File(outPath.substring(0, outPath.lastIndexOf(File.separator))); if (!ists()) {
file.mkdirs();
}
if (new File(outPath).isDirectory()) {
continue;
}
output = new FileOutputStream(outPath);
byte[] buf1 = new byte[2048];
int len;
while ((len = ad(buf1)) > 0) {
output.write(buf1, 0, len);
}
output.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (output != null) {
output.close();
}
if (zipInput != null) {
zipInput.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论