Java中 Package Sealing 的探秘之旅

转载自:https://blog.csdn.net/technerd/article/details/8945587

简介

        如果没有JAR (Java Archive)的存在,Java也许不至于像今天这样大行天下。JAR 是一个将其他文件或JAR捆绑在一起,并以.jar做为扩展名的文件。它使用与Zip相同的格式进行压缩,所以适用于任何支持Zip压缩的系统环境。由于是压缩格式,这使得它在网络上的传输十分迅速。JAR的manifest文件使得我们可以对JAR包进行数字签名,版本控制,包密封(Seal),和设置程序的执行入口等,另外,从Java 5 开始在META-INF文件夹下生成索引文件INDEX.LIST,可以大大缩短了JAR文件中类的加载时间。

       这篇文章主要探讨一下JAR中的Package Sealing功能。这个功能是在Java 1.2引入的。在生成Jar文件时我们可以指定是否将整个Jar或者其中某几个Package进行密封,如果是将Jar文件整个进行密封,那就意味着其内所有的Package都被密封了。Package一旦密封,那么Java 虚拟机一旦成功装载密封Package中的某个类后,其后所有装载的带有相同Package名的类必须来自同一个Jar文件,否则将触发Sealing Violation安全异常。闲话不多说,让我们在如下几个方面开始Package Sealing的探秘之旅吧。

Manifest的Package Sealing 配置

Java源代码解析 Sealing Violation 

Package Sealing 的益处

Sealing Violation实例


Manifest的Package Sealing 配置

我们先看看 JAR文件是如何通过manifest文件来对Jar文件中的Package进行密封。主要有两种密封方式

[plain] view plain copy

Manifest-Version: 1.0  


Name: com/tr/algorithm/test/box/  

Sealed: true  


Name: com/tr/algorithm/search/  

Sealed: true  


Name: com/tr/multiThreads/  

Sealed: true  


Name: com/tr/algorithm/BinaryTree/  

Sealed: true  

上面的manifest文件中针对某几个package进行了密封操作,但下面的manifest文件内容是对Jar文件中所有的package进行密封

[plain] view plain copy

Manifest-Version: 1.0  

Sealed: true  

Java源代码解析 Sealing Violation

知道了如何对Package进行密封后,我们一起来看看Java源代码中是如何触发Sealing Violation。我使用的JDK版本为1.6

[plain] view plain copy

java version "1.6.0_24"  

Java(TM) SE Runtime Environment (build 1.6.0_24-b50)  

Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode)  

下面是URLClassLoader中对Package Sealing进行安全校验的代码. 我添加了一些注释,方便理解.

[java] view plain copy

   String pkgname = name.substring(0, i);  

   Package pkg = getPackage(pkgname);  

   Manifest man = res.getManifest();  


if (pkg != null) {   

/*

                 * 如果当前试图加载的类,例如com.seal.util.DateUtil.java,所对应的Package

          *  例如com.seal.util,已经被ClassLoader装载。

                 */       

if (pkg.isSealed()) {  

/*

    *如果当前装载的类,例如com.seal.util.DateUtil.java,所对应的Package com.seal.util 已经被装载且密封,

    *但当前装载的类的URI和已经装载的Package不同,通俗一点就是来自不同的Jar文件的话

    *将触发安全异常。

    */  

if (!pkg.isSealed(url)) {  

throw new SecurityException(  

"sealing violation: package " + pkgname + " is sealed");  

    }  


}else { //如果这个Package已经被装载,但没有被密封。  

/*

    *查看当前正在装载的类,例如com.seal.util.DateUtil.java ,所对应的Package com.seal.util 是否需要密封。

    *如果是的话,将触发安全异常。因为,JVM不允许对已经装载的Package再进行密封。

    */  

if ((man != null) && isSealed(pkgname, man)) {   

throw new SecurityException(  

"sealing violation: can't seal package " + pkgname +   

": already loaded");  

    }  

}  

}else {  

/*

*如果当前试图加载的类对应的Package没有被ClassLoader装载,则试图加载该Package。

*如果Manifest文件非空,则会使用Manifest来定义这个Package,当然,这包含是否对Package进行密封。

*/  

if (man != null) {  

    definePackage(pkgname, man, url);  

}else {  

definePackage(pkgname,null, null, null, null, null, null, null);  

              }  

   }  

总的来说,有两种情况触发关于Sealing的安全异常

检查当前试图加载的类对应的Package是否已经被JVM装载且密封。如果已经被装载且密封了,但被密封的Package与当前加载的类对应的Package不是来自同一个Jar文件,将触发安全异常

检查当前试图加载的类对应的 Package是否已经被JVM装载且密封。如果已经被装载但没有被密封,但当前试图加载的类对应的Package确要试图进行密封操作,将触发安全异常。JVM不允许对已经装载但未密封的Package再进行密封操作。

Package Sealing 的益处

Package Sealing所能带来的好处主要是版本一致性. 我们知道Java 在运行时是严格按照classpath中定义的顺序进行装载和检查,尤其是现在Java开源包满天飞, 很有可能你的Java应用程序或者中间件的classpath中会在不同的Jar文件中包含同一个Package的不同版本。这会使得程序运行产生不一致性结果,很难发现。

Sealing Violation实例

我们来看看下面两个针对Sealing Violation的例子,就很容易明白。  

版本升级例子

第一例子的大背景是对发布的Jar包进行版本升级给用户使用。在版本一中我们有DateUtil和StringUtil 两个类

[java] view plain copy

package com.seal.util;  


public class DateUtil {  

public void DoDateStuff(){  

System.out.println("doing date stuffs in version 1.0");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class StringUtil {  

public void DoStringStuff(){  

System.out.println("doing string stuffs in version 1.0");  

    }  

}  

这两个类我们发布为sealed_v1.jar。 该Jar文件已经被密封,其Manifest文件内容如下

[plain] view plain copy

Manifest-Version: 1.0  

Sealed: true  

在版本二中,我们增加了一个类NumberUtil,同时更改了DateUtil和StringUtil的逻辑。版本二所发布的Jar文件为sealed_v2.jar。Manifest文件的内容和版本一相同。Java类代码如下

[java] view plain copy

package com.seal.util;  


public class DateUtil {  

public void DoDateStuff(){  

System.out.println("doing date stuffs in version 2.0");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class StringUtil {  

public void DoStringStuff(){  

System.out.println("doing string stuffs in version 2.0");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class NumberUtil {  

public void DoNumberStuff(){  

System.out.println("doing number stuffs in version 2.0");  

    }  

}  

这时候,对于客户而言,可用的Jar版本有版本一sealed_v1.jar和版本二sealed_v2.jar。用户有个测试类如下

[java] view plain copy

import com.seal.util.DateUtil;  

import com.seal.util.NumberUtil;  

import com.seal.util.StringUtil;  



public class SealTestCase1 {  


public static void main(String[] args){  

DateUtil du =new DateUtil();  

     du.DoDateStuff();  

StringUtil su =new StringUtil();  

     su.DoStringStuff();  


NumberUtil nu =new NumberUtil();  

     nu.DoNumberStuff();  

  }  

}  

我们执行Java命令编译并执行上面的测试类,假设上述两个Jar文件sealed_v1.jar和sealed_v2.jar,以及SealTestCase1类都存放在目录D:\sealingTest\case_1中。

执行如下命令编译SealTestCase1类。

javac -classpath .;D:\sealingTest\case_1\sealed_v1.jar;D:\sealingTest\case_1\sealed_v2.jar SealTestCase1.java

执行如下命令运行SealTestCase1类。

java -classpath .;D:\sealingTest\case_1\sealed_v1.jar;D:\sealingTest\case_1\sealed_v2.jar SealTestCase1

[plain] view plain copy

doing date stuffs in version 1.0  

doing string stuffs in version 1.0  

Exception in thread "main" java.lang.SecurityException: sealing violation: package com.seal.util is sealed  

        at java.net.URLClassLoader.defineClass(URLClassLoader.java:234)  

        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)  

        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)  

        at java.security.AccessController.doPrivileged(Native Method)  

        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)  

        at java.lang.ClassLoader.loadClass(ClassLoader.java:305)  

        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)  

        at java.lang.ClassLoader.loadClass(ClassLoader.java:246)  

        at SealTestCase1.main(SealTestCase1.java:14)  

这个Sealing Violation 正是JDK中定义的第一种情况。请看如下的代码注释来进一步理解这个Sealing Violation的产生的根本原因。

[java] view plain copy

public class SealTestCase1 {  


public static void main(String[] args){  

/*

      * JVM 装载 com.seal.util.DateUtil类,由于Classpath 中sealed_v1.jar在最前面,

      * 所以,com.seal.util.DateUtil类来自于sealed_v1.jar。我们知道sealed_v1.jar是被整个密封的。

      * JVM 会将Package com.seal.util 进行装载并密封。 

      */  

DateUtil du =new DateUtil();  

     du.DoDateStuff();  


/*

          * 同样,sealed_v1.jar在classpath中比sealed_v2.jar靠前,

          * 类 com.seal.util.StringUtil 也会在sealed_v1.jar中进行装载。

          * 由于Package com.seal.util之前已经被装载并密封,而且类com.seal.util.StringUtil与

          * 类com.seal.util.DateUtil 来自于相同的Jar文件。所以,不会出现什么问题。顺利通过安全管理器的校验。

          */  

StringUtil su =new StringUtil();  

     su.DoStringStuff();  


/*

      * JVM试图装载类com.seal.util.NumberUtil时,发现该类根本没在sealed_v1.jar中

      * 所以,继续查找sealed_v2.jar,并找到该类。

      * 但JVM发现之前装载的Package com.seal.util 已经被装载并密封,而且来自sealed_v1.jar,

      * 而这个类确来自sealed_v2.jar。这是JVM会报安全异常错误。

      */  

NumberUtil nu =new NumberUtil();  

     nu.DoNumberStuff();  

  }  

}  

了解到上面的原因后,我们可以将sealed_v1.jar和sealed_v2.jar在classpath中的位置调换,然后再执行一次

java -classpath .;D:\sealingTest\case_1\sealed_v2.jar;D:\sealingTest\case_1\sealed_v1.jar SealTestCase1

[plain] view plain copy

doing date stuffs in version 2.0  

doing string stuffs in version 2.0  

doing number stuffs in version 2.0  

我们看到结果正常显示。我们可以看到Package Sealing 保证了版本的一致性。

第三方发布 vs官方发布

第二个例子是某官方发布了一个密封的Jar文件 sealed_official_release.jar。其类如下

[java] view plain copy

package com.seal.util;  


public class DateUtil {  

public void DoDateStuff(){  

System.out.println("doing date stuffs officially");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class NumberUtil {  

public void DoNumberStuff(){  

System.out.println("doing number stuffs officially");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class StringUtil {  

public void DoStringStuff(){  

System.out.println("doing string stuffs officially");  

    }  

}  

然后,该官方发布的Jar文件,被某第三方组织更改和发布。Jar文件的名称为 unsealed_3rdparty_release.jar。请注意,与原来官方发布的Jar的另一个不同是这个Jar文件是一个未密封的Jar文件。内容如下,

[java] view plain copy

package com.seal.util;  


public class DateUtil {  

public void DoDateStuff(){  

System.out.println("doing modified date stuffs by third party");  

    }  

}  

[java] view plain copy

package com.seal.util;  


public class StringUtil {  

public void DoStringStuff(){  

System.out.println("doing modified string stuffs by third party");  

    }  

}  

那这时,用户手里可能有这两个不同的Jar文件,并且需要运行如下程序

[java] view plain copy

import com.seal.util.DateUtil;  

import com.seal.util.NumberUtil;  

import com.seal.util.StringUtil;  





public class SealTestCase2 {  

public static void main(String[] args){  

DateUtil du =new DateUtil();  

             du.DoDateStuff();  

StringUtil su =new StringUtil();  

             su.DoStringStuff();  


NumberUtil nu =new NumberUtil();  

             nu.DoNumberStuff();  

          }  

}  

我们假设上述两个Jar文件sealed_official_release.jar, unsealed_3rdparty_release.jar和类SealTestCase2.java 都在目录D:\sealingTest\case_2中

运行如下命令,编译类SealTestCase2.java。

javac -classpath .;D:\sealingTest\case_2\unsealed_3rdparty_release.jar;D:\sealingTest\case_2\sealed_official_release.jar SealTestCase2.java

运行如下命令运行SealTestCase2.java。

java -classpath .;D:\sealingTest\case_2\unsealed_3rdparty_release.jar;D:\sealingTest\case_2\sealed_official_release.jar SealTestCase2

[plain] view plain copy

doing modified date stuffs by third party  

doing modified string stuffs by third party  

Exception in thread "main" java.lang.SecurityException: sealing violation: can't  

 seal package com.seal.util: already loaded  

        at java.net.URLClassLoader.defineClass(URLClassLoader.java:242)  

        at java.net.URLClassLoader.access$000(URLClassLoader.java:58)  

        at java.net.URLClassLoader$1.run(URLClassLoader.java:197)  

        at java.security.AccessController.doPrivileged(Native Method)  

        at java.net.URLClassLoader.findClass(URLClassLoader.java:190)  

        at java.lang.ClassLoader.loadClass(ClassLoader.java:305)  

        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)  

        at java.lang.ClassLoader.loadClass(ClassLoader.java:246)  

        at SealTestCase2.main(SealTestCase2.java:15)  

这个Sealing Violation 正是JDK中定义的第二种情况。请看如下的代码注释来进一步理解这个Sealing Violation的产生的根本原因。

[java] view plain copy

public class SealTestCase2 {  

public static void main(String[] args){  

/*

              * JVM 装载 com.seal.util.DateUtil类,由于Classpath 中unsealed_3rdparty_release.jar在最前面,

              * 所以,com.seal.util.DateUtil类来自于unsealed_3rdparty_release.jar。

              * 我们知道unsealed_3rdparty_release.jar是没有被密封的。

              * JVM 会将Package com.seal.util 进行装载但不会密封它。 

              */  

DateUtil du =new DateUtil();  

             du.DoDateStuff();  

/*

                  * 同样,unsealed_3rdparty_release.jar在classpath中比sealed_official_release.jar靠前,

                  * 类 com.seal.util.StringUtil 也会在unsealed_3rdparty_release.jar中进行装载。

                  * 由于Package com.seal.util之前已经被装载但没有密封,

                  * 所以,不会出现什么问题。顺利通过安全管理器的校验。

                  */  


StringUtil su =new StringUtil();  

             su.DoStringStuff();  

/*

              * JVM试图装载类com.seal.util.NumberUtil时,发现该类根本没在unsealed_3rdparty_release.jar中

              * 所以,继续查找sealed_official_release.jar,并找到该类。请注意sealed_official_release.jar

              * 是被密封的。所以,JVM试图对Package com.seal.util 进行密封。

              * 但JVM发现Package com.seal.util 已经被装载。

              * 这时,JVM会报安全异常错误。

              */  

NumberUtil nu =new NumberUtil();  

             nu.DoNumberStuff();      

          }  


}  

让我们将Jar文件sealed_official_release.jar, unsealed_3rdparty_release.jar的顺序在classpath中调换位置,运行如下命令

java -classpath .;D:\sealingTest\case_2\sealed_official_release.jar;D:\sealingTest\case_2\unsealed_3rdparty_release.jar SealTestCase2

[java] view plain copy

doing date stuffs officially  

doing string stuffs officially  

doing number stuffs officially  

我们看到客户类SealTestCase2执行的所有类是来自官方发布的Jar文件。从而,保证了版本的一致性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容

  • 当然是你的父母啊 今天起来了个大早,去​医院做出国体检。我到时已经有十几人在候诊室等待了,因为提前预约的缘故我排...
    栗子糕抹茶酱阅读 402评论 0 0
  • 版本号:8.3.5 市场分析: 根据Analysys易观智库产业数据库最新发布的《中国在线旅游市场季度监测报告20...
    LOLITA_vip阅读 9,066评论 0 5
  • 他和她从小就认识的,因为他们彼此的父母都认识的,所以两家人经常串门。小时候他和她的父母常会把他和她凑在一起玩。...
    与萍水相逢阅读 334评论 1 2
  • 单强见铁一鸣控制了尖嘴猴腮,也就不参与打斗只站在旁边警戒。 铁一鸣推着尖嘴猴腮向蓝色头发靠去,并吼着:“来啊!来啊...
    李武_四川阅读 212评论 2 13
  • 前两日,校园在修草坪,一群背着草坪机、腰围一条薄毛毯、脚穿一双及膝靴的人像蚕吃桑叶一样将手中草坪机的扇叶伸向那片覆...
    追风去啦啦啦阅读 406评论 0 1