Java程序报签名不一致,怎样找出冲突的JAR包

Java程序报“签名不一致”,先从异常里提到的类名查起,找到它所属的包,再检查运行时加载这个包的几个JAR文件。如果同一个类加载器加载的同包类带有不同的签名者证书,或者一部分有签名、一部分没有,就会触发这类冲突。处理重点是依赖内容和加载来源,代码签名证书是否到期要另外核对。

这件事容易把人带偏:项目刚更新依赖,程序启动失败,报错又写着签名,于是大家开始检查证书订单。看到“signer information does not match”,把同一条异常中的完整类名抄下来,去部署目录找它的出处。

Java程序报签名不一致,怎样找出冲突的JAR包

一、先看清楚,Java正在比较什么

先别把JAR文件与Java包混为一谈。JAR是你能在电脑目录里找到、拿去部署的一份归档,里面装着编译后的类和资源。Java包描述的是类在程序中的归属,一个包下的读取类和写入类,完全能分别装进两份JAR。

假设异常点名demo.tools.Reader,Reader是类的名字,最后一个点号前的部分是包名。接着找该包里的其他类,沿着运行时的加载记录追到各自的JAR,才能把需要比较的文件列齐。

Oracle的ClassLoader文档规定了这项检查:同一个类加载器先定义某个包的第一个类,记下它的证书集合,随后再定义该包的类,就要拿新类的证书与这份记录比较。前者来自已签的原厂依赖,后者来自无签名的重编译文件,检查就过不去。

比如原厂换证前后的两个发行文件,显示的企业名称能够保持不变,所用证书却已经更换;你采购的CA品牌,也不是Java拿来识别同包签名者的依据。

二、把报错类所在的JAR找出来

复制报错时,从异常类型一直保留到后面的调用位置,重点标出点分格式的类名。发给同事的内容要能回答“到底哪个类被挡住了”,完整堆栈里还保留着触发它的调用链,排查时可以顺着往回找。

接着把演示类名写成条目路径demo/tools/Reader.class:点号换为斜杠,末尾补上类文件扩展名。清单查看命令是“jar tf library.jar”,其中的文件名要填实际部署的依赖路径,再从输出中搜索完整条目名。

找到一份以后,继续检查运行目录里的其他JAR。相同类名出现两次,就把两份文件的路径、版本和来源都记下来;即使没有重复类,也要查看同一Java包里是否分散着其他类。

JDK 9及以上可以在原启动命令中加入“-Xlog:class+load=info”,放在主类名或“-jar”之前。重新触发故障后,把日志里该包已加载的类与后面的来源路径一起摘出来;失败的类再到条目清单中查找,两边的记录合起来使用。

整理结果时,给每个候选文件写上“包含哪个类、放在哪个目录、日志是否显示已加载”。这张小表能让同事沿着相同路径复查,也能把同名文件的不同副本区分开。

三、逐个验签,再比较签名者

对查到的JAR执行“jarsigner -verify -verbose -certs library.jar”。这个命令会展开验证信息和签名者证书,方便对照各个文件。检查的是运行环境里的那份文件,别拿开发电脑上的同名副本替代。

把涉及的类条目单独列出来,旁边注明“原厂签署”“自行编译未签”或实际查到的其他状态。这样做,能把报错从一句抽象提示变成两份可检查的文件。如果两边都有签名,接着查看各条目究竟由哪些证书签署。

想继续辨认具体证书,使用“keytool -printcert -jarfile library.jar”查看SHA-256指纹,并把输出与jarsigner中的类条目相对应。验签记录列出多个签名者,就把它们全部纳入这次比较,别在看到第一张熟悉的证书后停下来。

还有一个容易漏掉的地方:本机与服务器的JDK是否对应,安全配置是否一致?这两项写进排查表,验签就按部署环境的设置执行。若输出提示算法已禁用、签名按未签名处理,应向组件发布方索取适配版本。

看到摘要校验失败,就另列一项处理:核对文件下载、替换和重新打包过程,取得完整的原始制品。摘要不符与同包证书不一致对应不同的排查路径,记录时写出实际报错,别统一归成“证书出错”。

四、依赖树看一遍,运行目录也看一遍

Maven项目执行“mvn dependency:tree”,沿报错组件往上找:它是自己写进pom.xml的,还是引入另一个组件时被一起带进来的?找到这条引入路径,才知道该修改哪项声明、在哪里排除旧版本。

需要注意的是,依赖树展示的是构建工具解析出的依赖关系。服务器上手工放进去的JAR、启动脚本额外加入的目录,以及应用容器提供的公共库,还要单独检查。

因此,排查时把构建结果和实际启动目录放在一起对照。项目已经换成新依赖,运行目录里却留着旧文件,就清理旧文件并重新部署完整制品。启动脚本指向另一套目录,就先改正路径,再重新观察加载日志。

调整依赖要有依据。同一组件同时出现不同版本,就核对项目及上层组件的兼容要求,确定一组能配套使用的版本。不要为了让目录看起来整齐,随手删除一个仍被程序使用的库。

调整后再生成依赖树,并检查部署目录和启动路径,确保项目声明、构建产物和实际加载文件对应。

五、合并JAR的项目,检查原签名怎样处理

如果项目把多个依赖合并成一个大JAR,就回头查看打包插件配置。原始依赖的签名与原始内容相对应,重新合并、改写类或处理资源后,要重新检查成品的签名结构。

检查新JAR里的META-INF,看看原依赖的签名元数据是否被带进来了。构建改动后,原签名核对的摘要与新内容不再吻合,验证就会失败。Maven Shade官方有对应过滤示例,用来在重新打包时排除旧签名文件。

给打包插件写过滤规则时,只处理这次要排除的旧签名文件,并检查实际使用的签名块格式。整个META-INF目录不能一并删掉:项目使用ServiceLoader发现服务实现类,就要保留META-INF/services里的声明,删掉会影响这项加载机制。

需要保留第三方原始签名,就按独立依赖的形式使用原始JAR,并处理同包类的签名者一致性。项目确定重新组合并以自身身份发布,就在构建配置中处理原签名元数据,对最终成品执行自己的签署和验证。

合并打包完成后,重新核对条目清单与签名结果。项目要求交付签名JAR,就把Java代码签名证书配置在最终发布步骤,前面的编译、依赖合并和资源处理先完成。

六、修好了以后,用实际启动方式再跑一次

排查结束,先清理旧构建输出,生成一套完整制品,再部署到测试环境。启动命令、JDK版本、依赖目录和目标部署环境保持对应,重新执行原来触发异常的功能。

复测要把故障当时的操作走完,比如选中同一类待导入文件、进入对应功能,再执行处理。有些类要等业务真正用到才加载,发布人员应看到相关步骤完成,并确认日志中的类来源符合这次调整。

同时保存这次采用的依赖版本、文件路径、验签输出和测试结果。下一次升级同一组件时,团队就有一份能对照的记录:更换了哪个文件,原来是谁签的,新文件来自哪里。

Java程序签名不一致排查常见问题

1、两个JAR分别验签成功,为什么一起运行还报错?

单独验证成功,只说明各自文件的签名检查通过。把两份依赖放进程序以后,加载器还要按包核查证书集合;某个包内的类来自不同签名者,就会被拦下。定位时将加载路径与相关类的验签输出放在一起分析。

2、怎样确认两个JAR使用的签名者证书是否一致?

用keytool查看证书详情和SHA-256指纹,再用jarsigner核对涉及的类条目。JAR存在多签名或部分条目未签名时,按相关类对应的证书集合逐项比较。

3、改一下JAR文件名,能避开签名冲突吗?

把文件重命名以后,里面的类和签名仍是原来的。只要启动配置仍能找到并加载这份文件,遇到那组同包类时,原有冲突就还在。要处理的是启动过程选中的那份依赖,以及它与同包其他类的组合。

4、重新编译了依赖,为什么更容易出现冲突?

自行编译的文件与原厂制品要分别核对。如果新文件没有原厂签名,却与仍带原厂签名的同包类一起加载,就会出现证书集合不一致。

5、排查时应该把哪些材料交给技术支持?

提供完整异常堆栈、JDK版本、启动命令、依赖树及相关JAR的验签输出。注明文件来源与实际路径,并移除命令和日志中的账号密码等敏感信息。

相关阅读:软件签名不一致怎么解决

相关文章