本文翻譯自Oracle官方文檔,原文地址html
Java™ Archive (JAR) 文件格式使你可以把多個文件打包進一個歸檔文件中,一個Jar文件一般包括多個類文件和任意數量的資源文件。java
使用Jar文件格式,咱們能夠得到這些好處:git
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 |
命令格式小程序
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包中(遞歸處理目錄)c
和f
的順序沒有要求,可是兩者之間不能有空格。數組
執行該命令後,會在當前目錄下生成一個壓縮的JAR文件,該命令會生成一個默認的manifest文件(在JAR中的META-INF目錄下)。瀏覽器
注意:JAR 文件中的元數據(例如條目名稱、註釋和清單內容)必須以 UTF8 編碼
除了c
和f
外,還有一些選項:
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類文件,一些音頻文件和一些圖片,結構以下圖所示:
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
查看JAR文件內容的命令格式:
jar tf jar-file
命令選項說明:
t
表示要查看JAR文件內容f
表示在命令行上指定要查看的JAR文件jar-file
參數表示JAR文件的路徑和名稱t
和f
的順序沒有要求,可是兩者之間不能有空格。
該命令會將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
抽取文件命令以下:
jar xf jar-file [archived-file(s)]
說明:
x
選項表示要抽取JAR包中的文件f
表示要抽取的JAR包文件是經過命令行參數指定的,而不是標準輸入jar-file
參數是指要抽取的JAR包文件名稱archived-file(s)
是一個可選的參數,表示要抽取的目標文件,多個文件用英文逗號分割;若是不指定該參數,則抽取JAR包中的全部文件x
和 f
的順序沒有要求,可是兩者之間不能有空格。
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 JAR包不會有任何改變。
若是你想抽取JAR包中的所有文件,使用下面這個命令:
jar xf TicTacToe.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目錄下。
咱們可使用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
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
JAR文件支持各類功能,包含電子簽名、版本控制、包密封等等。是什麼讓 JAR 文件具備這種多功能性?答案是 JAR 文件的清單。
manifest是一個特殊文件,包含有關打包在 JAR 文件中的文件信息。經過定製manifest中包含的「元」信息,您可使 JAR 文件用於多種用途。
經過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文件。
建立JAR文件時,能夠經過m
選項向manifest文件中添加自定義信息。
你能夠經過修改向menifest文件,來開啓一些特殊功能,好比說包密封。一般,修改manifest文件涉及添加具備特定用途的header數據,以容許 JAR 文件執行特定的所需功能。
修改manifest文件的第一步是,建立一個txt文件,其中包含你要添加的條目(header: value),而後使用m
選項將文件內容添加到manifest中。
注意:txt文件必須以換行符或者回車符結束,不然將不能被正確的解析。
命令格式以下:
jar cfm jar-file manifest-addition input-file(s)
解釋說明:
注意:manifest的內容必須以 UTF-8 編碼。
若是你有一個打包成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
假設咱們有一個含有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
建立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
有時你可能須要在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 中的類如今已加載到類路徑中。
您可能須要在 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.
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 文件,請使用值爲 true 的 Sealed manifest header。例如:
Sealed: true
指定此存檔中的全部包都是密封的,除非爲清單條目中具備 Sealed 屬性的特定包顯式覆蓋。
在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 屬性。
您能夠選擇使用您的電子「簽名」對 JAR 文件進行簽名。驗證您的簽名的用戶能夠授予您的 JAR 捆綁軟件安全權限,您也能夠驗證要使用的已簽名 JAR 文件的簽名。
Java™ 平臺使您可以對 JAR 文件進行數字簽名。您對文件進行數字簽名的緣由與使用筆和墨水簽署紙質文檔的緣由相同——讓讀者知道您編寫了該文檔,或者至少該文檔已得到您的批准。
例如,當您簽署一封信時,每一個認出您簽名的人均可以確認這封信是您寫的。一樣,當您對文件進行數字簽名時,「識別」您的數字簽名的任何人都知道該文件來自您。 「識別」電子簽名的過程稱爲驗證。
對 JAR 文件進行簽名後,您還能夠選擇爲簽名加蓋時間戳。與在紙質文檔上放置日期相似,簽名時間戳標識 JAR 文件的簽名時間。時間戳可用於驗證 JAR 文件證書在簽署時是否有效。
對文件進行簽名和驗證的能力是 Java 平臺安全架構的重要組成部分。安全性由在運行時生效的安全策略控制。您能夠配置策略以向小程序和應用程序授予安全權限。例如,您能夠授予小程序執行一般禁止的操做的權限,例如讀取和寫入本地文件或運行本地可執行程序。若是您下載了一些由受信任實體簽名的代碼,您可使用該事實做爲決定將哪些安全權限分配給代碼的標準。
一旦您(或您的瀏覽器)確認小程序來自可信來源,您就可讓平臺放寬安全限制,讓小程序執行一般被禁止的操做。受信任的小程序能夠具備由有效策略文件指定的自由。
Java 平臺經過使用稱爲公鑰和私鑰的特殊數字來啓用簽名和驗證。公鑰和私鑰是成對出現的,它們起到互補的做用。
私鑰是您能夠用來簽署文件的電子「筆」。顧名思義,您的私鑰只有您本身知道,所以其餘人沒法「僞造」您的簽名。使用您的私鑰簽名的文件只能經過相應的公鑰進行驗證。
然而,僅公鑰和私鑰不足以真正驗證簽名。即便您已經驗證簽名文件包含匹配的密鑰對,您仍然須要某種方式來確認公鑰實際上來自真正的簽名者。
所以,還須要一個元素來進行簽名和驗證工做。該附加元素是簽名者包含在已簽名 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 目錄中。與清單文件或簽名文件不一樣,簽名塊文件不是人類可讀的。
簽名塊文件包含兩個驗證必不可少的元素:
簽名塊文件名一般具備 .DSA 擴展名,代表它們是由默認數字簽名算法建立的。若是使用與某些其餘標準算法關聯的密鑰進行簽名,則其餘文件擴展名是可能的。
有關密鑰、證書和證書頒發機構的其餘信息,請參閱
有關 Java 平臺安全架構的更多信息,請參閱如下相關文檔:
您可使用 JAR 簽名和驗證工具對 JAR 文件進行簽名併爲簽名添加時間戳。您可使用 jarsigner 命令調用 JAR 簽名和驗證工具,所以咱們將其簡稱爲「Jarsigner」。
要簽署 JAR 文件,您必須首先擁有一個私鑰。私鑰及其相關的公鑰證書存儲在稱爲keystores的受密碼保護的數據庫中。keystores能夠保存許多潛在簽名者的密鑰。keystores中的每一個密鑰均可以經過別名來標識,別名一般是擁有密鑰的簽名者的姓名。例如,屬於 Rita Jones 的密鑰可能具備別名「rita」。
簽名 JAR 文件的命令的基本形式是
jarsigner jar-file alias
命令說明:
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 文件的示例。在這些示例中,咱們將假設如下內容:
在這些假設下,您可使用此命令對名爲 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
?.
一般,驗證簽名的 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文件視爲未簽名。
Java平臺提供了一些與JAR相關的API:
爲了讓您瞭解這些新 API 帶來的可能性,本課程將引導您瞭解名爲 JarRunner 的示例應用程序的內部工做原理。
JarRunner 使您可以經過在命令行上指定 JAR 文件的 URL 來運行捆綁在 JAR 文件中的應用程序。例如,若是名爲 TargetApp 的應用程序捆綁在位於 http://www.example.com/TargetApp.jar 的 JAR 文件中,您可使用如下命令運行該應用程序:
java JarRunner http://www.example.com/TargetApp.jar
爲了讓 JarRunner 工做,它必須可以執行如下任務,全部這些任務都是經過使用新的 API 來完成的:
JarRunner 應用程序由兩個類組成,JarRunner 和 JarClassLoader。 JarRunner 將大部分 JAR 處理任務委託給 JarClassLoader 類。 JarClassLoader 擴展了 java.net.URLClassLoader 類。在繼續本課程以前,您能夠瀏覽 JarRunner 和 JarClassLoader 類的源代碼:
JarClassLoader 類擴展了 java.net.URLClassLoader。顧名思義,URLClassLoader 旨在用於加載經過搜索一組 URL 訪問的類和資源。 URL 能夠引用目錄或 JAR 文件。
除了繼承 URLClassLoader 以外,JarClassLoader 還利用了另外兩個與 JAR 相關的新 API,java.util.jar 包和 java.net.JarURLConnection 類中的特性。在本節中,咱們將詳細介紹 JarClassLoader 的構造函數和兩個方法。
構造函數將 java.net.URL 的實例做爲參數。傳遞給此構造函數的 URL 將在 JarClassLoader 的其餘地方使用,以查找要從中加載類的 JAR 文件。
public JarClassLoader(URL url) { super(new URL[] { url }); this.url = url; }
URL 對象被傳遞給超類 URLClassLoader 的構造函數,它接受一個 URL[] 數組,而不是單個 URL 實例,做爲參數。
使用 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; }
getMainClassName 方法使用 java.net.JarURLConnection 類指定的 JAR URL 格式。 JAR 文件 URL 的語法以下例所示:
終止符 !/ 分隔符表示 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。
此類表示應用程序和 JAR 文件之間的通訊連接。它具備訪問 JAR 文件清單的方法。 getMainClassName 的第二行是:
JarURLConnection uc = (JarURLConnection)u.openConnection();
在這個語句中,第一行中建立的 URL 實例打開了一個 URLConnection。而後將 URLConnection 實例轉換爲 JarURLConnection,以便它能夠利用 JarURLConnection 的 JAR 處理功能。
經過對 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,用於指定其餘標準清單標頭。)
咱們已經看到 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 的教程來回顧反射。
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 將加載應用程序的入口類,向它傳遞任何參數,而後啓動應用程序。