Packaging Programs in JAR Files

本文翻译自Oracle官方文档,原文地址html

Java™ Archive (JAR) 文件格式使你可以把多个文件打包进一个归档文件中,一个Jar文件一般包括多个类文件和任意数量的资源文件。java

使用Jar文件格式,咱们能够得到这些好处:git

  • 安全:你能够对Jar文件内容进行数字签名,识别签名的用户能够有选择地授予你的软件一些权限,不然他不会这么作地(不相信你的软件)
  • 减小下载时间:若是你的Applet被打包成一个Jar文件,那么浏览器只须要一个http链接便可下载Applet中的各类类文件和资源文件
  • 压缩:Jar格式支持对文件进行压缩,节约存储空间
  • 扩展性:扩展框架提供了一种向 Java 核心平台添加功能的方法,JAR 文件格式定义了扩展的打包。经过使用 JAR 文件格式,你能够将你的JAR文件变成扩展包。扩展框架介绍
  • 包密封:存储在 JAR 文件中的包能够被密封,以便强制包版本保存一致性。在 JAR 文件中密封包意味着必须在同一个 JAR 文件中找到在该包中定义的全部类。
  • 包版本控制:JAR 文件能够保存有关其包含的文件的数据,例如供应商和版本信息
  • 可移植性:Jar文件处理机制时Java平台核心API的标准部分

1. 如何使用Jar包

JAR 文件采用 ZIP 文件格式打包,因此你能够对Jar文件执行数据压缩、存档、解压缩和存档解包等操做。JDK提供一个名字Java Archive Tool的工具,来帮助咱们完成这些操做,因为Java Archive Tool工具经过 jar 命令来执行,所以又被叫作Jar工具算法

下面的表描述了jar的常见操做数据库

操做 命令
建立Jar文件 jar cf jar-file input-file(s)
查看Jar文件内容 jar tf jar-file
解压Jar文件 jar xf jar-file
解压Jar包中的特定文件 jar xf jar-file archived-file(s)
运行Jar包 (manifest文件中必须存在 Main-class 头信息) java -jar app.jar

1.1 建立JAR文件

命令格式小程序

jar cf jar-file input-file(s)windows

命令选项说明:api

  • c表示要建立JAR文件
  • f表示输出到文件而不是标准输出
  • jar-file表示JAR文件名称
  • input-file(s)表示要添加到JAR包中的文件,多个文件用逗号分割;input-file(s)能够包含通配符;若是input-file(s)是目录,那么目录下文件都会被添加到Jar包中(递归处理目录)

cf的顺序没有要求,可是两者之间不能有空格。数组

执行该命令后,会在当前目录下生成一个压缩的JAR文件,该命令会生成一个默认的manifest文件(在JAR中的META-INF目录下)。浏览器

注意:JAR 文件中的元数据(例如条目名称、注释和清单内容)必须以 UTF8 编码

除了cf外,还有一些选项:

Option Description
v 打印JAR文件构建过程当中的详细信息,好比添加到JAR包中的文件名称
0 (zero) 不要对文件进行压缩
M 不要生成默认的manifest文件
m 用于合并已有的manifest文件内容,格式:jar cmf jar-file existing-manifest input-file(s)
-C 改变命令执行时的目录

注意:生成的JAR文件中回包含建立时间,所以,当你重复建立JAR文件时,即便文件内容不变,但生成的JAR也不会彻底相同;推荐在manifest中使用版本信息而不是建立时间来控制JAR文件的版本。

命令使用示例

假设咱们有一个叫TicTacToe的java applet,它包含一个java类文件,一些音频文件和一些图片,结构以下图所示:

TicTacToe目录结构

audio和image目录下面存放的是一些音频文件和一些图片。

进入TicTacToe目录,执行下面这个命令,把全部文件打包至一个名为TicTacToe.jar的文件中。

jar cvf TicTacToe.jar TicTacToe.class audio images

audio和image是目录,jar工具会进行递归处理,把目录下的文件所有放入JAR文件中,生成的JAR文件位于当前目录。因为使用了v选项,所以会看到相似下面的输出

adding: TicTacToe.class (in=3825) (out=2222) (deflated 41%)
adding: audio/ (in=0) (out=0) (stored 0%)
adding: audio/beep.au (in=4032) (out=3572) (deflated 11%)
adding: audio/ding.au (in=2566) (out=2055) (deflated 19%)
adding: audio/return.au (in=6558) (out=4401) (deflated 32%)
adding: audio/yahoo1.au (in=7834) (out=6985) (deflated 10%)
adding: audio/yahoo2.au (in=7463) (out=4607) (deflated 38%)
adding: images/ (in=0) (out=0) (stored 0%)
adding: images/cross.gif (in=157) (out=160) (deflated -1%)
adding: images/not.gif (in=158) (out=161) (deflated -1%)

从上面的输出结果能够看出文件被压缩了,Jar工具默认会文件进行压缩,你可使用0选项关闭此行为:

jar cvf0 TicTacToe.jar TicTacToe.class audio images

某些状况下,你可能想禁制文件的压缩,好比提升浏览器加载JAR文件的速度,未压缩的文件一般加载的更快,由于解压文件须要时间的。凡事有利也有弊,禁止压缩会会致使文件下载时间更长(由于文件更大了 )。

Jar工具接受通配符*做为参数,若是你压缩的当前目录下的全部文件,你可使用这个命令建立JAR包:

jar cvf TicTacToe.jar *

Jar工具会自动生成一个manifest文件,路径为META-INF/MANNIREST.MF.

在上面的例子中,JAR包中的文件仍然保持原来的目录结构和名称。你可使用C选项来改变文件在JAR包中的路径。

假如你想要 音频文件和图像文件放在JAR中的一级目录下,你可使用下面这个命令来建立JAR包:

jar cf ImageAudio.jar -C images . -C audio .

-C images 指示Jar工具进入images目录下,而 -C images 后面的 . 指示Jar工具将images目录的文件所有放在当前文件夹下,-C audio . 的处理逻辑相似,生成的JAR包文件内容以下:

META-INF/MANIFEST.MF
cross.gif
not.gif
beep.au
ding.au
return.au
yahoo1.au
yahoo2.au

相比之下,若是你不使用-C选项,jar cf ImageAudio.jar images audio,生成的JAR包内容以下:

META-INF/MANIFEST.MF
images/cross.gif
images/not.gif
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au

1.2 查看JAR文件内容

查看JAR文件内容的命令格式:

jar tf jar-file

命令选项说明:

  • t表示要查看JAR文件内容
  • f表示在命令行上指定要查看的JAR文件
  • jar-file参数表示JAR文件的路径和名称

tf的顺序没有要求,可是两者之间不能有空格。

该命令会将JAR文件的目录内容输出到标准输出上。

你可使用v选项,输出更多信息,包括文件大小,最后的修改时间。

命令使用示例

假设咱们要查看之间建立的TicTacToe.jar文件的内容:

jar tf TicTacToe.jar

输出结果以下:

META-INF/MANIFEST.MF
TicTacToe.class
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
images/
images/cross.gif
images/not.gif

其中不只有类文件TicTacToe、audio、images等目录,还有自动生成的manifest文件META-INF/MANIFEST.MF

无论你使用的是哪一种平台或操做系统,输出的文件路径都是用正斜杠/。JAR文件中的路径老是相对的,你不会看到相似C:的开头。

若是你使用v选项,jar tvf TicTacToe.jar,将看到更多详细的输出内容:

68 Thu Nov 01 20:00:40 PDT 2012 META-INF/MANIFEST.MF
   553 Mon Sep 24 21:57:48 PDT 2012 TicTacToe.class
  3708 Mon Sep 24 21:57:48 PDT 2012 TicTacToe.class
  9584 Mon Sep 24 21:57:48 PDT 2012 TicTacToe.java
     0 Mon Sep 24 21:57:48 PDT 2012 audio/
  4032 Mon Sep 24 21:57:48 PDT 2012 audio/beep.au
  2566 Mon Sep 24 21:57:48 PDT 2012 audio/ding.au
  6558 Mon Sep 24 21:57:48 PDT 2012 audio/return.au
  7834 Mon Sep 24 21:57:48 PDT 2012 audio/yahoo1.au
  7463 Mon Sep 24 21:57:48 PDT 2012 audio/yahoo2.au
   424 Mon Sep 24 21:57:48 PDT 2012 example1.html
     0 Mon Sep 24 21:57:48 PDT 2012 images/
   157 Mon Sep 24 21:57:48 PDT 2012 images/cross.gif
   158 Mon Sep 24 21:57:48 PDT 2012 images/not.gif

1.3 抽取JAR包中的文件

抽取文件命令以下:

jar xf jar-file [archived-file(s)]

说明:

  • x 选项表示要抽取JAR包中的文件
  • f 表示要抽取的JAR包文件是经过命令行参数指定的,而不是标准输入
  • jar-file 参数是指要抽取的JAR包文件名称
  • archived-file(s) 是一个可选的参数,表示要抽取的目标文件,多个文件用英文逗号分割;若是不指定该参数,则抽取JAR包中的全部文件

xf 的顺序没有要求,可是两者之间不能有空格。

Jar工具会把抽取的文件放到当前工做目录下,并保留他们在JAR包中的目录结构,原始的JAR包不会有任何改变。

注意:抽取JAR包中的文件时,会覆盖当前目录下的同名文件

命令使用示例

上面咱们建立的TicTacToe JAR包的文件内容以下:

META-INF/MANIFEST.MF
TicTacToe.class
TicTacToe.class
TicTacToe.java
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
example1.html
images/
images/cross.gif
images/not.gif

假设咱们想抽取出TicTacToe类文件和images目录中的cross.gif文件,你可使用下面这个命令:

jar xf TicTacToe.jar TicTacToe.class images/cross.gif

该命令作两件事:

  • 拷贝TicTacToe.class文件至当前目录
  • 建立images目录(若是它不存在),拷贝cross.git文件至images目录下

原始的TicTacToe JAR包不会有任何改变。

若是你想抽取JAR包中的所有文件,使用下面这个命令:

jar xf TicTacToe.jar

1.4 更新JAR文件

你可使用u 选项来修改JAR包中的清单文件manifest,或者向JAR包中添加文件。

命令格式以下:

jar uf jar-file input-file(s)

说明:

  • u 表示你想要更新存在的JAR包
  • f 表示在命令行上指定要更新的 JAR 文件
  • jar-file 指要更新的JAR包
  • input-file(s) 指要添加到JAR包中的文件,多个文件用空格分割

注意:JAR包中的同名文件会被覆盖。

命令使用实例

咱们的 TicTacToe.jar文件内容以下:

META-INF/MANIFEST.MF
TicTacToe.class
TicTacToe.class
TicTacToe.java
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
example1.html
images/
images/cross.gif
images/not.gif

如今让咱们来把一个新文件 images/new.gif 添加到JAR包中,命令以下:

jar uf TicTacToe.jar images/new.gif

如今 TicTacToe.jar文件内容变成了这样:

META-INF/MANIFEST.MF
TicTacToe.class
TicTacToe.class
TicTacToe.java
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
example1.html
images/
images/cross.gif
images/not.gif
images/new.gif

能够看到,最后一行是咱们刚才新加入的文件。

你也可使用-C选项改变文件目录:

jar uf TicTacToe.jar -C images new.gif

如今的文件内容变成了下面这样:

META-INF/MANIFEST.MF
META-INF/MANIFEST.MF
TicTacToe.class
TicTacToe.class
TicTacToe.java
audio/
audio/beep.au
audio/ding.au
audio/return.au
audio/yahoo1.au
audio/yahoo2.au
example1.html
images/
images/cross.gif
images/not.gif
new.gif

注意:new.gif文件在最外层了,不是在images目录下。

1.5 运行JAR文件

咱们可使用JAVA启动器(java命令),执行打包后的JAR文件:

java -jar jar-file

你只能指定一个jar文件。

在执行运行命令前,你必须确保在JAR包中的manifest文件中指定了应用的运行入口,格式以下:

Main-Class: classname

classname就是咱们的java应用运行入口类,其中包含一个main方法。关于manifest文件,后面会详细说明。

设置好Main-Class后,咱们就能够运行JAR包了:

java -jar app.jar

若是要运行的jar包在其它目录下,你必须指定完整的路径,像这样:java -jar path/app.jar

1.6 命令详细说明文档

window平台:https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jar.html

Linux平台:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jar.html

2. Manifest文件使用说明

JAR文件支持各类功能,包含电子签名、版本控制、包密封等等。是什么让 JAR 文件具备这种多功能性?答案是 JAR 文件的清单。

manifest是一个特殊文件,包含有关打包在 JAR 文件中的文件信息。经过定制manifest中包含的“元”信息,您可使 JAR 文件用于多种用途。

2.1 默认的manifest文件

经过jar命令建立的JAR文件时,它会自动生成一个manifest文件。JAR文件中只能有一个menifest文件,且路径为:META-INF/MANIFEST.MF

默认的manifest文件内容大概长这样:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)

manifest文件内容格式为header: value

manifest中也能够包含JAR文件中其它的文件信息,这取决于你如何使用JAR文件。

2.2 修改manifest文件

建立JAR文件时,能够经过m选项向manifest文件中添加自定义信息。

你能够经过修改向menifest文件,来开启一些特殊功能,好比说包密封。一般,修改manifest文件涉及添加具备特定用途的header数据,以容许 JAR 文件执行特定的所需功能。

修改manifest文件的第一步是,建立一个txt文件,其中包含你要添加的条目(header: value),而后使用m选项将文件内容添加到manifest中。

注意:txt文件必须以换行符或者回车符结束,不然将不能被正确的解析。

命令格式以下:

jar cfm jar-file manifest-addition input-file(s)

解释说明:

  • c表示要建立JAR文件
  • f指定文件名称
  • m表示要合并存在的文件内容至manifest文件中
  • jar-file表示要建立的JAR文件名称
  • manifest-addition表示要合并的文件
  • input-file(s)表示要添加到JAR文件中的文件

注意:manifest的内容必须以 UTF-8 编码。

2.3 设置应用(JAR文件)执行入口

若是你有一个打包成JAR文件的应用,那么你须要以某种方式指定JAR包中某个文件做为应用的入口。你能够在manifest文件中提供一个Main-Class做为header的条目,格式为:

Main-Class: classname

其中classname是你的入口点(位于JAR文件中)的全限定名。入口点是一个具备签名 public static void main(String[] args) 的方法的类。

设置完Main-Class后,就可使用java命令执行改JAR文件了:

java -jar myapp.jar

使用示例1

假设咱们有一个含有main方法的类文件MyClass,其位于包MyPackage下。

首先,建立一个名称Manifest.txt的文件,内容为:

Main-Class: MyPackage.MyClass

注意:文件必须以换行符或者回车符结束,不然将不能被正确的解析。

而后执行命令生成JAR文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

MyJar.jar文件中的清单文件内容为:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Main-Class: MyPackage.MyClass

最后,你能够执行下面的命令,容许该JAR包:

java -jar MyJar.jar

使用示例2

建立JAR文件时,咱们可使用e选项,覆盖Main-Class属性的内容,好比:

jar cfe app.jar MyApp MyApp.class

Main-Class 的值为MyApp,如今你能够直接执行app.jar:

java -jar app.jar

若是入口点类位于包下,则须要使用.(点)字符做为分隔符。例如, Main.class 位于名为 foo 的包中,则能够经过如下方式指定入口点:

jar cfe Main.jar foo.Main foo/Main.class

2.4 在JAR文件的ClassPath中添加类

有时你可能须要在JAR文件中引用其它JAR文件中的类,在manifest文件中存在一个名叫Class-Path的header,能够帮助咱们实现这个需求,格式以下:

Class-Path: jar1-name jar2-name directory-name/jar3-name

经过Class-Path,你能够避免使用-classpath

注意:Class-Path 指向本地网络上的类或 JAR 文件,而不是 JAR 文件中的 JAR 文件或可经过 Internet 协议访问的类。要将 JAR 文件中 JAR 文件中的类加载到类路径中,您必须编写自定义代码来加载这些类。例如,若是 MyJar.jar 包含另外一个名为 MyUtils.jar 的 JAR 文件,则不能使用 MyJar.jar 清单中的 Class-Path 标头将 MyUtils.jar 中的类加载到类路径中。

使用示例

假如咱们想在MyJar.jar中加载MyUtils.jar中的类,注意:这两个JAR在相同目录下

首先,建立一个名叫Manifest.txt的文件,内容以下:

Class-Path: MyUtils.jar

注意:文件必须以换行符或者回车符结束,不然将不能被正确的解析。

而后,使用下面的命令建立JAR文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将建立带有清单的 JAR 文件,其中包含如下内容:

Manifest-Version: 1.0
Class-Path: MyUtils.jar
Created-By: 1.7.0_06 (Oracle Corporation)

当你运行 MyJar.jar 时,MyUtils.jar 中的类如今已加载到类路径中。

2.5 设置包版本信息

您可能须要在 JAR 文件的清单中包含包版本信息。您在清单中使用如下标头提供此信息:

Header Definition
Name The name of the specification.
Specification-Title The title of the specification.
Specification-Version The version of the specification.
Specification-Vendor The vendor of the specification.
Implementation-Title The title of the implementation.
Implementation-Version The build number of the implementation.
Implementation-Vendor The vendor of the implementation.

能够为每一个包分配一组此类标题。版本header应直接出如今包的 Name header下方。此示例显示全部版本控制标头:

Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

使用示例

咱们但愿在 MyJar.jar 的清单中包含上面示例中的标头。

咱们首先建立一个名为 Manifest.txt 的文本文件,内容以下:

Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util 
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

注意:文件必须以换行符或者回车符结束,不然将不能被正确的解析。

而后咱们经过输入如下命令建立一个名为 MyJar.jar 的 JAR 文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将建立带有清单的 JAR 文件,其中包含如下内容:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: java/util/
Specification-Title: Java Utility Classes
Specification-Version: 1.2
Specification-Vendor: Example Tech, Inc.
Implementation-Title: java.util 
Implementation-Version: build57
Implementation-Vendor: Example Tech, Inc.

2.6 在 JAR 文件中密封包

JAR 文件中的包能够选择密封,这意味着该包中定义的全部类必须存档在同一个 JAR 文件中。例如,您可能想要密封一个包,以确保软件中类之间的版本一致性。

您能够经过在清单文件中添加 Sealed 头信息来密封 JAR 文件中的包,通常形式为:

Name: myCompany/myPackage/
Sealed: true

其中myCompany/myPackage/ 是要密封的包的名称。注意:包名称必须以“/”结尾。

使用示例

咱们要在 JAR 文件 MyJar.jar 中密封两个包 firstPackage 和 secondPackage。

咱们首先建立一个名为 Manifest.txt 的文本文件,内容以下:

Name: myCompany/firstPackage/
Sealed: true

Name: myCompany/secondPackage/
Sealed: true

注意:文件必须以换行符或者回车符结束,不然将不能被正确的解析。

而后咱们经过输入如下命令建立一个名为 MyJar.jar 的 JAR 文件:

jar cfm MyJar.jar Manifest.txt MyPackage/*.class

这将建立带有清单的 JAR 文件,其中包含如下内容:

Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Name: myCompany/firstPackage/
Sealed: true
Name: myCompany/secondPackage/
Sealed: true

密封 JAR 文件

若是要保证包中的全部类都来自相同的代码源,请使用 JAR 密封。密封 JAR 指定该 JAR 定义的全部包都是密封的,除非在每一个包的基础上被覆盖。

要密封 JAR 文件,请使用值为 true 的 Sealed manifest header。例如:

Sealed: true

指定此存档中的全部包都是密封的,除非为清单条目中具备 Sealed 属性的特定包显式覆盖。

2.7 使用清单(manifest)属性加强安全性

在JAR文件中的清单文件中,添加如下属性,能够增长应用的安全性,其中只有Permissions属性是必须的:

  • Permissions 属性用于确保应用程序仅请求在用于调用应用程序的小程序标记或 JNLP 文件中指定的权限级别。使用此属性有助于防止有人从新部署使用您的证书签名的应用程序并以不一样的权限级别运行它。此属性在主 JAR 文件的清单中是必需的。有关更多信息,请参阅 Java Platform, Standard Edition Deployment Guide 中的 Permissions Attribute

  • Codebase 属性用于确保 JAR 文件的代码库仅限于特定域。使用此属性可防止有人出于恶意目的在另外一个网站上从新部署您的应用程序。有关更多信息,请参阅 Java 平台标准版部署指南中的Codebase

  • Application-Name 属性用于提供在已签名应用程序的安全提示中显示的标题。有关更多信息,请参阅 Java Platform, Standard Edition Deployment Guide 中的 Application-Name 属性

  • Application-Library-Allowable-Codebase 属性用于标识您的应用程序应该被找到的位置。当 JAR 文件位于与 JNLP 文件或 HTML 页面不一样的位置时,使用此属性可减小安全提示中显示的位置数。有关更多信息,请参阅 Java Platform, Standard Edition Deployment Guide 中的 Application-Library-Allowable-Codebase 属性

  • Caller-Allowable-Codebase 属性用于标识 JavaScript 代码能够从中调用应用程序的域。使用此属性可防止未知的 JavaScript 代码访问您的应用程序。有关更多信息,请参阅 Java Platform, Standard Edition Deployment Guide 中的 Caller-Allowable-Codebase 属性

  • Entry-Point 属性用于标识容许用做 RIA 入口点的类。使用此属性可防止从 JAR 文件中的其余可用入口点运行未经受权的代码。有关更多信息,请参阅 Java 平台标准版部署指南中的 Entry-Point 属性

  • Trusted-Only 属性用于防止加载不受信任的组件。有关更多信息,请参阅 Java Platform, Standard Edition Deployment Guide 中的 Trusted-Only 属性

  • Trusted-Library 属性用于容许特权 Java 代码和沙箱 Java 代码之间的调用,而无需提示用户许可。有关更多信息,请参阅 Java 平台标准版部署指南中的 Trusted-Library 属性

3. 签名、验证JAR文件

您能够选择使用您的电子“签名”对 JAR 文件进行签名。验证您的签名的用户能够授予您的 JAR 捆绑软件安全权限,您也能够验证要使用的已签名 JAR 文件的签名。

3.1 什么是签名、验证

Java™ 平台使您可以对 JAR 文件进行数字签名。您对文件进行数字签名的缘由与使用笔和墨水签署纸质文档的缘由相同——让读者知道您编写了该文档,或者至少该文档已得到您的批准。

例如,当您签署一封信时,每一个认出您签名的人均可以确认这封信是您写的。一样,当您对文件进行数字签名时,“识别”您的数字签名的任何人都知道该文件来自您。 “识别”电子签名的过程称为验证。

对 JAR 文件进行签名后,您还能够选择为签名加盖时间戳。与在纸质文档上放置日期相似,签名时间戳标识 JAR 文件的签名时间。时间戳可用于验证 JAR 文件证书在签署时是否有效。

对文件进行签名和验证的能力是 Java 平台安全架构的重要组成部分。安全性由在运行时生效的安全策略控制。您能够配置策略以向小程序和应用程序授予安全权限。例如,您能够授予小程序执行一般禁止的操做的权限,例如读取和写入本地文件或运行本地可执行程序。若是您下载了一些由受信任实体签名的代码,您可使用该事实做为决定将哪些安全权限分配给代码的标准。

一旦您(或您的浏览器)确认小程序来自可信来源,您就可让平台放宽安全限制,让小程序执行一般被禁止的操做。受信任的小程序能够具备由有效策略文件指定的自由。

Java 平台经过使用称为公钥和私钥的特殊数字来启用签名和验证。公钥和私钥是成对出现的,它们起到互补的做用。

私钥是您能够用来签署文件的电子“笔”。顾名思义,您的私钥只有您本身知道,所以其余人没法“伪造”您的签名。使用您的私钥签名的文件只能经过相应的公钥进行验证。

然而,仅公钥和私钥不足以真正验证签名。即便您已经验证签名文件包含匹配的密钥对,您仍然须要某种方式来确认公钥实际上来自真正的签名者。

所以,还须要一个元素来进行签名和验证工做。该附加元素是签名者包含在已签名 JAR 文件中的证书。证书是来自公认的证书颁发机构的数字签名声明,代表谁拥有特定的公钥。证书颁发机构是整个行业都信任的实体(一般是专门从事数字安全的公司),能够为密钥及其全部者签署和颁发证书。对于签名的 JAR 文件,证书代表谁拥有 JAR 文件中包含的公钥。

当您签署 JAR 文件时,您的公钥与相关证书一块儿放在存档中,以便任何想要验证您的签名的人均可以轻松使用它。

数字签名总结:

  • 签名者使用私钥对 JAR 文件进行签名。
  • 相应的公钥与其证书一块儿放置在 JAR 文件中,以便任何想要验证签名的人均可以使用它。

摘要和签名文件

当您签署 JAR 文件时,存档中的每一个文件都会在存档的清单中得到一个摘要条目。可能长这样:

Name: test/classes/ClassOne.class
SHA1-Digest: TD1GZt8G11dXY2p4olSZPc5Rj64=

摘要值是文件内容在签名时的散列或编码表示。当且仅当文件自己发生变化时,文件的摘要才会发生变化。

对 JAR 文件进行签名后,会自动生成一个签名文件并将其放置在 JAR 文件的 META-INF 目录中,该目录与包含存档清单的目录相同。签名文件的文件名带有 .SF 扩展名。如下是签名文件内容的示例:

Signature-Version: 1.0
SHA1-Digest-Manifest: h1yS+K9T7DyHtZrtI+LxvgqaMYM=
Created-By: 1.7.0_06 (Oracle Corporation)

Name: test/classes/ClassOne.class
SHA1-Digest: fcav7ShIG6i86xPepmitOVo4vWY=

Name: test/classes/ClassTwo.class
SHA1-Digest: xrQem9snnPhLySDiZyclMlsFdtM=

Name: test/images/ImageOne.gif
SHA1-Digest: kdHbE7kL9ZHLgK7akHttYV4XIa0=

Name: test/images/ImageTwo.gif
SHA1-Digest: mF0D5zpk68R4oaxEqoS9Q7nhm60=

如您所见,签名文件包含存档文件的摘要条目,这些条目看起来相似于清单中的摘要值条目。然而,虽然清单中的摘要值是根据文件自己计算的,但签名文件中的摘要值是根据清单中的相应条目计算的。签名文件还包含整个清单的摘要值(请参阅上面示例中的 SHA1-Digest-Manifest 标头)。

在验证已签名的 JAR 文件时,会从新计算其每一个文件的摘要,并与清单中记录的摘要进行比较,以确保 JAR 文件的内容自签名后未更改。做为额外的检查,清单文件自己的摘要值被从新计算并与签名文件中记录的值进行比较。

您能够在 JDK™ 文档的清单格式页面上阅读有关签名文件的其余信息。

签名块文件

除了签名文件以外,签名块文件会在 JAR 文件签名时自动放置在 META-INF 目录中。与清单文件或签名文件不一样,签名块文件不是人类可读的。

签名块文件包含两个验证必不可少的元素:

  • 使用签名者的私钥生成的 JAR 文件的数字签名
  • 包含签名者公钥的证书,供任何想要验证已签名 JAR 文件的人使用

签名块文件名一般具备 .DSA 扩展名,代表它们是由默认数字签名算法建立的。若是使用与某些其余标准算法关联的密钥进行签名,则其余文件扩展名是可能的。

相关文档

有关密钥、证书和证书颁发机构的其余信息,请参阅

有关 Java 平台安全架构的更多信息,请参阅如下相关文档:

3.2 签名JAR 文件

您可使用 JAR 签名和验证工具对 JAR 文件进行签名并为签名添加时间戳。您可使用 jarsigner 命令调用 JAR 签名和验证工具,所以咱们将其简称为“Jarsigner”。

要签署 JAR 文件,您必须首先拥有一个私钥。私钥及其相关的公钥证书存储在称为keystores的受密码保护的数据库中。keystores能够保存许多潜在签名者的密钥。keystores中的每一个密钥均可以经过别名来标识,别名一般是拥有密钥的签名者的姓名。例如,属于 Rita Jones 的密钥可能具备别名“rita”。

签名 JAR 文件的命令的基本形式是

jarsigner jar-file alias

命令说明:

  • jar-file 是要签名的 JAR 文件的路径名。
  • alias 是标识用于签署 JAR 文件的私钥的别名,以及密钥的关联证书。

Jarsigner 工具将提示您输入keystores和别名的密码。

此命令的基本形式假定要使用的keystores位于主目录中名为 .keystore 的文件中。它将分别建立名为 x.SF 和 x.DSA 的签名和签名块文件,其中 x 是别名的前八个字母,所有转换为大写。此基本命令将使用签名的 JAR 文件覆盖原始 JAR 文件。

实际上,您可能但愿使用一个或多个可用的命令选项。例如,鼓励对签名进行时间戳记,以便用于部署应用程序的任何工具均可以验证用于签署 JAR 文件的证书在签署文件时是否有效。若是不包含时间戳,则 Jarsigner 工具会发出警告。

在 jar-file以前能够添加一些选项,下表描述了可用的选项:

Option Description
-keystore url 若是您不想使用 .keystore 默认数据库,则指定要使用的keystores。
-sigfile file 指定 .SF 和 .DSA 文件的基本名称,而不是使用别名的前八个字母。文件只能由大写字母 (A-Z)、数字 (0-9)、连字符 (-) 和下划线 (_) 组成。
-signedjar file 若是您不但愿原始未签名文件被签名文件覆盖,则指定要生成的已签名 JAR 文件的名称。
-tsa url 使用 URL 标识的时间戳机构 (TSA) 为签名生成时间戳。
-tsacert alias 使用由别名标识的 TSA 公钥证书为签名生成时间戳。
-altsigner class 指示使用替代签名机制对签名进行时间戳记。彻底限定的类名标识所使用的类。
-altsignerpath classpathlist 提供由 altsigner 选项标识的类的路径以及该类所依赖的任何 JAR 文件。

使用示例

让咱们看几个使用 Jarsigner 工具签署 JAR 文件的示例。在这些示例中,咱们将假设如下内容:

  • 您的别名是“johndoe”
  • 您要使用的keystores位于当前工做目录中名为“mykeys”的文件中
  • 您要用于为签名添加时间戳的 TSA 位于 http://tsa.url.example.com

在这些假设下,您可使用此命令对名为 app.jar 的 JAR 文件进行签名:

jarsigner -keystore mykeys -tsa http://tsa.url.example.com app.jar johndoe

系统将提示您输入keystores和别名的密码。因为此命令未使用 -sigfile 选项,所以它建立的 .SF 和 .DSA 文件将命名为 JOHNDOE.SF 和 JOHNDOE.DSA。因为该命令不使用 -signedjar 选项,所以生成的签名文件将覆盖 app.jar 的原始版本。

让咱们看看若是您使用不一样的选项组合会发生什么:

jarsigner -keystore mykeys -sigfile SIG -signedjar SignedApp.jar 
          -tsacert testalias app.jar johndoe

签名和签名块文件将分别命名为 SIG.SF 和 SIG.DSA,而且签名的 JAR 文件 SignedApp.jar 将放置在当前目录中。原始未签名的 JAR 文件将保持不变。此外,签名将带有 TSA 的公钥证书的时间戳,标识为 testalias。

JAR 签名和验证工具的详细说明:安全工具摘要

注意:当证书是自签名证书时,UNKNOWN 将显示为应用程序的发布者。更多信息请参考 Is it safe to run an application from a publisher that is listed as UNKNOWN?.

3.3 验证签名的 JAR 文件

一般,验证签名的 JAR 文件将由您的 Java™ 运行时环境负责。您的浏览器将验证它下载的签名小程序。使用解释器的 -jar 选项调用的签名应用程序将由运行时环境验证。

可是,您可使用 jarsigner 工具自行验证已签名的 JAR 文件。例如,您可能想要这样作,以测试您准备的已签名 JAR 文件。

用于验证已签名 JAR 文件的基本命令是:

jarsigner -verify jar-file

此命令将验证 JAR 文件的签名并确保存档中的文件自签名后未更改。若是验证成功,您将看到如下消息:

jar verified.

若是您尝试验证未签名的 JAR 文件,则会产生如下消息:

jar is unsigned. (signatures missing or not parsable)

若是验证失败,则会显示相应的消息。例如,若是 JAR 文件的内容在 JAR 文件签名后发生了更改,那么在验证该文件时,会出现相似于如下内容的消息:

jarsigner: java.lang.SecurityException: invalid SHA1 
signature file digest for test/classes/Manifest.class

注意:若是签名JAR文件使用java.home/lib/Security/java.Security文件(其中java.home是安装JRE的目录)中JDK.JAR.disabledAlgorithms安全属性中指定的任何算法,则JDK将签名JAR文件视为未签名。

4. 使用JAR相关的API

Java平台提供了一些与JAR相关的API:

为了让您了解这些新 API 带来的可能性,本课程将引导您了解名为 JarRunner 的示例应用程序的内部工做原理。

An Example - The JarRunner Application

JarRunner 使您可以经过在命令行上指定 JAR 文件的 URL 来运行捆绑在 JAR 文件中的应用程序。例如,若是名为 TargetApp 的应用程序捆绑在位于 http://www.example.com/TargetApp.jar 的 JAR 文件中,您可使用如下命令运行该应用程序:

java JarRunner http://www.example.com/TargetApp.jar

为了让 JarRunner 工做,它必须可以执行如下任务,全部这些任务都是经过使用新的 API 来完成的:

  • 访问远程 JAR 文件并与其创建通讯连接
  • 检查 JAR 文件的清单以查看存档中的哪一个类是主类
  • 加载 JAR 文件中的类

JarRunner 应用程序由两个类组成,JarRunner 和 JarClassLoader。 JarRunner 将大部分 JAR 处理任务委托给 JarClassLoader 类。 JarClassLoader 扩展了 java.net.URLClassLoader 类。在继续本课程以前,您能够浏览 JarRunner 和 JarClassLoader 类的源代码:

4.1 JarClassLoader 类

JarClassLoader 类扩展了 java.net.URLClassLoader。顾名思义,URLClassLoader 旨在用于加载经过搜索一组 URL 访问的类和资源。 URL 能够引用目录或 JAR 文件。

除了继承 URLClassLoader 以外,JarClassLoader 还利用了另外两个与 JAR 相关的新 API,java.util.jar 包和 java.net.JarURLConnection 类中的特性。在本节中,咱们将详细介绍 JarClassLoader 的构造函数和两个方法。

JarClassLoader构造方法

构造函数将 java.net.URL 的实例做为参数。传递给此构造函数的 URL 将在 JarClassLoader 的其余地方使用,以查找要从中加载类的 JAR 文件。

public JarClassLoader(URL url) {
    super(new URL[] { url });
    this.url = url;
}

URL 对象被传递给超类 URLClassLoader 的构造函数,它接受一个 URL[] 数组,而不是单个 URL 实例,做为参数。

getMainClassName方法

使用 JAR 捆绑应用程序的 URL 构造 JarClassLoader 对象后,它须要一种方法来肯定 JAR 文件中的哪一个类是应用程序的入口点。这就是 getMainClassName 方法的工做:

public String getMainClassName() throws IOException {
    URL u = new URL("jar", "", url + "!/");
    JarURLConnection uc = (JarURLConnection)u.openConnection();
    Attributes attr = uc.getMainAttributes();
    return attr != null
                   ? attr.getValue(Attributes.Name.MAIN_CLASS)
                   : null;
}

您可能还记得在上一课中,JAR 捆绑应用程序的入口点由 JAR 文件清单的 Main-Class 标头指定。要了解 getMainClassName 如何访问 Main-Class 标头值,让咱们详细查看该方法,特别注意它使用的新 JAR 处理功能:

public String getMainClassName() throws IOException {
        URL u = new URL("jar", "", url + "!/");
        JarURLConnection uc = (JarURLConnection)u.openConnection();
        Attributes attr = uc.getMainAttributes();
        return attr != null ? attr.getValue(Attributes.Name.MAIN_CLASS) : null;
    }

The JarURLConnection class and JAR URLs

getMainClassName 方法使用 java.net.JarURLConnection 类指定的 JAR URL 格式。 JAR 文件 URL 的语法以下例所示:

jar:http://www.example.com/jarfile.jar!/

终止符 !/ 分隔符表示 URL 指的是整个 JAR 文件。分隔符后面的任何内容都指的是特定的 JAR 文件内容,以下例所示:

jar:http://www.example.com/jarfile.jar!/mypackage/myclass.class

getMainClassName 方法的第一行是:

URL u = new URL("jar", "", url + "!/");

此语句构造一个表示 JAR URL 的新 URL 对象,将 !/ 分隔符附加到用于建立 JarClassLoader 实例的 URL。

The java.net.JarURLConnection class

此类表示应用程序和 JAR 文件之间的通讯连接。它具备访问 JAR 文件清单的方法。 getMainClassName 的第二行是:

JarURLConnection uc = (JarURLConnection)u.openConnection();

在这个语句中,第一行中建立的 URL 实例打开了一个 URLConnection。而后将 URLConnection 实例转换为 JarURLConnection,以便它能够利用 JarURLConnection 的 JAR 处理功能。

获取清单(Manifest)属性: java.util.jar.Attributes

经过对 JAR 文件打开 JarURLConnection,您可使用 JarURLConnection 的 getMainAttributes 方法访问 JAR 文件清单中的头信息。此方法返回 java.util.jar.Attributes 的一个实例,该类将 JAR 文件清单中的标头名称与其关联的字符串值进行映射。 getMainClassName 中的第三行建立了一个 Attributes 对象:

Attributes attr = uc.getMainAttributes();

要获取清单的 Main-Class 标头(header)的值,getMainClassName 的第四行调用 Attributes.getValue 方法:

return attr != null
               ? attr.getValue(Attributes.Name.MAIN_CLASS)
               : null;

该方法的参数 Attributes.Name.MAIN_CLASS 指定它是您想要的 Main-Class 标头的值。 (Attributes.Name 类还提供静态字段,例如 MANIFEST_VERSION、CLASS_PATH 和 SEALED,用于指定其余标准清单标头。)

The invokeClass Method

咱们已经看到 JarURLClassLoader 如何识别 JAR 捆绑应用程序中的主类。最后一个要考虑的方法是 JarURLClassLoader.invokeClass,它容许调用主类来启动 JAR 捆绑的应用程序:

public void invokeClass(String name, String[] args)
    throws ClassNotFoundException,
           NoSuchMethodException,
           InvocationTargetException
{
    Class c = loadClass(name);
    Method m = c.getMethod("main", new Class[] { args.getClass() });
    m.setAccessible(true);
    int mods = m.getModifiers();
    if (m.getReturnType() != void.class || !Modifier.isStatic(mods) ||
        !Modifier.isPublic(mods)) {
        throw new NoSuchMethodException("main");
    }
    try {
        m.invoke(null, new Object[] { args });
    } catch (IllegalAccessException e) {
        // This should not happen, as we have disabled access checks
    }
}

invokeClass 方法接受两个参数:应用程序入口类的名称和要传递给入口类的 main 方法的字符串参数数组。首先,加载主类:

Class c = loadClass(name);

loadClass 方法继承自 java.lang.ClassLoader。

加载主类后,将使用 java.lang.reflect 包的反射 API 将参数传递给该类并启动它。你能够参考反射 API 的教程来回顾反射。

4.2 JarRunner 类

JarRunner 应用程序使用如下形式的命令启动:

java JarRunner url [arguments]

在上一节中,咱们已经看到 JarClassLoader 如何可以从给定的 URL 识别和加载 JAR 捆绑应用程序的主类。所以,要完成 JarRunner 应用程序,咱们须要可以从命令行获取 URL 和任何参数,并将它们传递给 JarClassLoader 的实例。这些任务属于 JarRunner 类,JarRunner 应用程序的入口点。

它首先从命令行上指定的 URL 建立一个 java.net.URL 对象:

public static void main(String[] args) {
    if (args.length < 1) {
        usage();
    }
    URL url = null;
    try {
        url = new URL(args[0]);
    } catch (MalformedURLException e) {
        fatal("Invalid URL: " + args[0]);
    }

若是 args.length < 1,则表示没有在命令行中指定 URL,所以会打印一条用法消息。若是第一个命令行参数是一个有效的 URL,则会建立一个新的 URL 对象来表示它。

接下来,JarRunner 建立一个 JarClassLoader 的新实例,将命令行中指定的 URL 传递给构造函数:

JarClassLoader cl = new JarClassLoader(url);

正如咱们在上一节中看到的, JarClassLoader 提供了用于处理JAR的方法。

传递给 JarClassLoader 构造函数的 URL 是您要运行的 JAR 捆绑应用程序的 URL。 JarRunner 接下来调用类加载器的 getMainClassName 方法来识别应用程序的入口类:

String name = null;
try {
    name = cl.getMainClassName();
} catch (IOException e) {
    System.err.println("I/O error while loading JAR file:");
    e.printStackTrace();
    System.exit(1);
}
if (name == null) {
    fatal("Specified jar file does not contain a 'Main-Class'" +
          " manifest attribute");
}

关键语句是:name = cl.getMainClassName()。其余语句用于错误处理。

一旦 JarRunner 肯定了应用程序的入口类,只剩下两个步骤:将任何参数传递给应用程序并启动应用程序。 JarRunner 使用如下代码执行如下步骤:

// Get arguments for the application
String[] newArgs = new String[args.length - 1];
System.arraycopy(args, 1, newArgs, 0, newArgs.length);
// Invoke application's main class
try {
    cl.invokeClass(name, newArgs);
} catch (ClassNotFoundException e) {
    fatal("Class not found: " + name);
} catch (NoSuchMethodException e) {
    fatal("Class does not define a 'main' method: " + name);
} catch (InvocationTargetException e) {
    e.getTargetException().printStackTrace();
    System.exit(1);
}

回想一下,第一个命令行参数是 JAR 捆绑应用程序的 URL。所以,要传递给该应用程序的任何参数都位于 args 数组中的第一个元素以后的。 JarRunner 获取这些元素,并建立一个名为 newArgs 的新数组以传递给应用程序。 JarRunner 而后将入口类名和新参数列表newArgs 传递给 JarClassLoader 的 invokeClass 方法。正如咱们在上一节中看到的,invokeClass 将加载应用程序的入口类,向它传递任何参数,而后启动应用程序。

相关文章
相关标签/搜索