jar MANIFEST.MF
Manifest-Version: 1.0
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Implementation-Title: dapi-engine
Implementation-Version: 2.0.0
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
Start-Class: com.XXX.XXXX.XXX.XXX.XXApplication
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Build-Jdk-Spec: 1.8
Spring-Boot-Version: 2.6.3
Created-By: Maven JAR Plugin 3.2.2
Main-Class: org.springframework.boot.loader.JarLauncher
书写注意:
1.每行的“:”(冒号)用来分隔键值对,冒号后边一定要跟一个空格.
2.MANIFEST.MF清单文件必须以一个空白行结束.
3.Class-Path里边的内容用空格分隔而不是逗号或者分号。
4.每行不能超过七十多的字符.
清单文件 - 维基百科 --- Manifest file - Wikipedia
HTML5 中的缓存清单是一个伴随 Web 应用程序的纯文本文件,可帮助 Web 应用程序在没有网络连接时运行。缓存机制读取该文件并确保其内容在本地可用。 HTML5 缓存清单的内容类型设置为“text/cache-manifest”
webmanifest 是渐进式 Web 应用程序中使用的 JSON 文件,使它们可以通过 URL 轻松共享、可由搜索引擎发现,并简化复杂的安装过程。此外,PWA 支持本机应用程序样式的交互和导航,包括添加到主屏幕、显示启动屏幕等。
应用程序和组装清单
在 Microsoft Windows 中,依赖 Windows Side-by-Side (WinSxS) 的软件需要应用程序清单,它是嵌入在可执行文件中或包含在随附的单独 XML 文件中的 XML 文档。它包含名称、版本、信任信息、执行所需的权限以及对其他组件的依赖关系。 [2]
程序集清单与应用程序清单非常相似,但描述了称为“程序集”的组件的标识。这些程序集在应用程序清单中引用。 [3]
应用程序清单的示例如下。该应用程序清单有两个核心部分:安全性和依赖性。安全部分表示应用程序需要“asInvoker”安全级别;也就是说,它可以在执行的任何安全级别上运行。
依赖部分表示应用程序需要一个名为“Microsoft.VC90.CRT”且版本号为“9.0.21022.8”的组件。
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns='urn:schemas-microsoft-com:asm.v1' manifestVersion='1.0'>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<!--I am okay with whatever security privilege level-->
<requestedExecutionLevel level='asInvoker' uiAccess='false' />
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<!--I need Microsoft Visual C++ 2008 Runtime to run-->
<assemblyIdentity type='win32' name='Microsoft.VC90.CRT' version='9.0.21022.8' processorArchitecture='x86' publicKeyToken='1fc8b3b9a1e18e3b' />
</dependentAssembly>
</dependency>
</assembly>
包清单
Linux 发行版严重依赖包管理系统来分发软件。在此方案中,包是包含清单文件的存档文件。主要目的是枚举发行版中包含的文件,以供各种打包工具处理或供人类使用。清单可能包含附加信息;例如,在 JAR(一种用于交付用 Java 编程语言编写的软件的包格式)中,它们可以指定版本号和执行入口点。清单可以选择包含每个文件的加密哈希或校验和。通过为此类清单文件创建加密签名,可以验证分发包的全部内容的真实性和完整性,因为更改任何文件都会使清单文件中的校验和无效。
修改清单文件
在创建 JAR 文件期间,您可以使用 m 命令行选项将自定义信息添加到清单中。本节介绍 m 选项。
Jar 工具会自动将路径名为 META-INF/MANIFEST.MF 的默认清单放入您创建的任何 JAR 文件中。您可以通过修改默认清单来启用特殊的 JAR 文件功能,例如包密封。通常,修改默认清单涉及向清单添加特殊用途的标头,以允许 JAR 文件执行特定的所需功能。
要修改清单,您必须首先准备一个文本文件,其中包含要添加到清单中的信息。然后,您可以使用 Jar 工具的 m 选项将文件中的信息添加到清单中。
警告:用于创建清单的文本文件必须以换行符或回车符结尾。如果最后一行不以换行符或回车符结尾,则将无法正确解析它。
基本命令的格式如下:
jar cfm jar-file manifest-addition input-file(s)
让我们看看该命令中使用的选项和参数:
- c 选项表示您要创建 JAR 文件。
- m 选项指示您要将现有文件中的信息合并到您正在创建的 JAR 文件的清单文件中。
- f 选项表示您希望输出到文件(您正在创建的 JAR 文件)而不是标准输出。
- manifest-addition 是现有文本文件的名称(或路径和名称),您要将其内容添加到 JAR 文件清单的内容中。
- jar-file 是您希望生成的 JAR 文件具有的名称。
- input-file(s) 参数是一个以空格分隔的列表,其中包含要放入 JAR 文件中的一个或多个文件。
m 和 f 选项的顺序必须与相应参数的顺序相同。
注意:清单的内容必须采用 UTF-8 编码。
设置应用程序的入口点
如果您有一个捆绑在 JAR 文件中的应用程序,则需要某种方法来指示 JAR 文件中的哪个类是您的应用程序的入口点。您可以通过清单中的 Main-Class
标头提供此信息,其一般格式为:
Main-Class: classname
值 classname
是作为应用程序入口点的类的名称。
回想一下,入口点是一个具有签名 public static void main(String[] args)
方法的类。
在清单中设置 Main-Class
标头后,您可以使用以下形式的 java
命令运行 JAR 文件:
java -jar JAR-name
执行 Main-Class
标头中指定的类的 main
方法。
一个例子
我们希望在运行 JAR 文件时执行包 MyPackage
中的类 MyClass
中的 main
方法。
我们首先创建一个名为 Manifest.txt
的文本文件,其中包含以下内容:
Main-Class: MyPackage.MyClass
警告:文本文件必须以换行符或回车符结束。如果最后一行不以换行符或回车符结尾,则将无法正确解析它。
然后,我们通过输入以下命令创建一个名为 MyJar.jar
的 JAR 文件:
jar cfm MyJar.jar Manifest.txt MyPackage/*.class
这将创建 JAR 文件,其清单包含以下内容:
Manifest-Version: 1.0
Created-By: 1.7.0_06 (Oracle Corporation)
Main-Class: MyPackage.MyClass
当您使用以下命令运行 JAR 文件时, MyClass
的 main
方法将执行:
java -jar MyJar.jar
使用 JAR 工具设置入口点
“e”标志(代表“入口点”)创建或覆盖清单的 Main-Class
属性。它可以在创建或更新 JAR 文件时使用。使用它来指定应用程序入口点,而无需编辑或创建清单文件。
例如,此命令创建 app.jar
,其中清单中的 Main-Class
属性值设置为 MyApp
:
jar cfe app.jar MyApp MyApp.class
您可以通过运行以下命令直接调用该应用程序:
java -jar app.jar
如果入口点类名位于包中,则可以使用“.” (点)字符作为分隔符。例如,如果 Main.class
位于名为 foo
的包中,则可以通过以下方式指定入口点:
将类添加到 JAR 文件的类路径
您可能需要从 JAR 文件中引用其他 JAR 文件中的类。
例如,在典型情况下,小应用程序捆绑在 JAR 文件中,该 JAR 文件的清单引用了不同的 JAR 文件(或几个不同的 JAR 文件),这些文件充当用于该小应用程序的实用程序。
您可以指定要包含在小程序或应用程序清单文件的 Class-Path 标头字段中的类。 Class-Path 标头采用以下形式:
Class-Path: jar1-name jar2-name directory-name/jar3-name
通过在清单中使用 Class-Path 标头,您可以避免在调用 Java 来运行应用程序时指定长 -classpath 标志。
注意: Class-Path 标头指向本地网络上的类或 JAR 文件,而不是 JAR 文件中的 JAR 文件或可通过 Internet 协议访问的类。要将 JAR 文件中的 JAR 文件中的类加载到类路径中,您必须编写自定义代码来加载这些类。例如,如果 MyJar.jar 包含另一个名为 MyUtils.jar 的 JAR 文件,则无法使用 MyJar.jar's 清单中的 Class-Path 标头来加载 < b5> 进入类路径。
一个例子
我们希望将 MyUtils.jar 中的类加载到类路径中,以便在 MyJar.jar 中使用。这两个 JAR 文件位于同一目录中。
我们首先创建一个名为 Manifest.txt 的文本文件,其中包含以下内容:
Class-Path: MyUtils.jar
警告:文本文件必须以换行符或回车符结束。如果最后一行不以换行符或回车符结尾,则将无法正确解析它。
然后,我们通过输入以下命令创建一个名为 MyJar.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 中的类现在会加载到类路径中。
Java| MANIFEST.MF讲解_jafanece.mlif-CSDN博客
一般属性介绍
Manifest-Version
:
用来定义manifest文件的版本,例如:Manifest-Version: 1.0Main-Class
定义jar文件的入口类,该类必须是一个可执行的类(包含main方法的类),一旦定义了该属性即可通过java -jar x.jar
来运行该jar文件。
运行Jar:java -jar yveshe.jar
当运行上述命令时JVM将在yveshe.jar文件中的MANIFEST.MF文件中查找Main-Class属性的值,并尝试运行该类。如果在yveshe.jar文件中未包含Main-Class属性,则上述命令将生成错误。Class-Path
:
指定jar包的依赖关系,class loader会依据这个路径来搜索class.默认是相对路径,相对该jar所在的父文件夹.
可以在其manifest 文件中为JAR文件设置CLASSPATH。属性名称叫作类路径,必须在自定义清单文件中指定。 它是一个空格分隔的jar文件,zip文件和目录的列表。(不区分系统都是以空格来分隔多个jar文件)以下是一个属性配置例子:Class-Path: reference1.jar file:/c:/book/ http://www.yveshe.com/reference2.jar
这条命令配置了该yveshe.jar
依赖了三个jar:
一个JAR文件reference1.jar,一个使用文件协议文件的目录:c:/book/和另一个使用HTTP协议的JAR文件http://www.yveshe.com/reference2.jar
注意: 当使用java命令使用-jar(比如java -jar yveshe.jar)选项运行JAR文件时,将忽略Jar中 manifest文件之外的任何CLASSPATH设置。Sealed
:
密封 JAR 文件中的一个包意味着在这个包中定义的所有类都必须在同一个 JAR 文件中找到。这使包的作者可以增强打包类之间的版本一致性。密封还提供了防止代码篡改的手段。
要密封包,需要在 JAR 的 manifest 文件中为包添加一个 Name 头,然后加上值为“true”的 Sealed 头。与可执行的 JAR 一样,可以在创建 JAR 时,通过指定一个具有适当头元素的 manifest 文件密封一个 JAR,如下所示:Name: com/yveshe/
Sealed: true
Name 头标识出包的相对路径名。它以一个“/”结束以与文件名区别。在 Name 头后面第一个空行之前的所有头都作用于在 Name 头中指定的文件或者包。在上述例子中,因为 Sealed 头出现在 Name 头后并且中间没有空行,所以 Sealed 头将被解释为只应用到包 com/yveshe上。如果试图从密封包所在的 JAR 文件以外的其他地方装载密封包中的一个类,那么 JVM 将抛出一个SecurityException 。文件头Name的值为该封装的相对路径名。注意,该路径名由‘/’结束以区别于文件名。
在下面的代码中Sealed仅仅作用于name: com/yveshe/
意思是包com.yveshe
下的代码不允许被外部类加载调用.
Manifest-Version: 1.0
Class-Path: . cmd_lib/commons-lang3-3.7.jar
Main-Class: com.yveshe.PackageClass
Name: java/util/
Specification-Title: "Java Utility Classes"
Specification-Version: "1.2"
Specification-Vendor: "Sun Microsystems, Inc.".
Implementation-Title: "java.util"
Implementation-Version: "build57"
Implementation-Vendor: "Sun Microsystems, Inc."
name: com/yveshe/
Sealed: true
书写注意:
1.每行的“:”(冒号)用来分隔键值对,冒号后边一定要跟一个空格.
2.MANIFEST.MF清单文件必须以一个空白行结束.
3.Class-Path里边的内容用空格分隔而不是逗号或者分号。
4.每行不能超过七十多的字符.
设置包版本信息
您可能需要在 JAR 文件的清单中包含包版本信息。您可以在清单中使用以下标头提供此信息:
标头 | 定义 |
---|---|
Name | 规范的名称。 |
Specification-Title | 规范的标题。 |
Specification-Version | 规范的版本。 |
Specification-Vendor | 规范的供应商。 |
Implementation-Title | 实施的标题。 |
Implementation-Version | 实施的内部版本号。 |
Implementation-Vendor | 实施的供应商。 |
可以将一组此类标头分配给每个包。版本控制标头应直接出现在包的 Name 标头下方。此示例显示所有版本控制标头:
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 文件清单属性可帮助确保 applet 或 Java Web Start 应用程序的安全。仅 Permissions 属性是必需的。
-
Permissions 属性用于确保应用程序仅请求用于调用应用程序的 applet 标记或 JNLP 文件中指定的权限级别。
使用此属性有助于防止某人重新部署使用您的证书签名的应用程序并以不同的权限级别运行它。主 JAR 文件的清单中需要此属性。有关更多信息,请参阅《Java 平台标准版部署指南》中的权限属性。
-
Codebase 属性用于确保 JAR 文件的代码库仅限于特定域。使用此属性可以防止有人出于恶意目的在另一个网站上重新部署您的应用程序。有关更多信息,请参阅《Java 平台标准版部署指南》中的代码库属性。
-
Application-Name 属性用于提供签名应用程序的安全提示中显示的标题。有关更多信息,请参阅《Java 平台标准版部署指南》中的应用程序名称属性。
-
Application-Library-Allowable-Codebase 属性用于标识预期找到您的应用程序的位置。当 JAR 文件位于与 JNLP 文件或 HTML 页面不同的位置时,使用此属性可以减少安全提示中显示的位置数。有关更多信息,请参阅《Java 平台标准版部署指南》中的 Application-Library-Allowable-Codebase 属性。
-
Caller-Allowable-Codebase 属性用于标识 JavaScript 代码可以从哪些域调用您的应用程序。使用此属性可以防止未知的 JavaScript 代码访问您的应用程序。有关更多信息,请参阅《Java 平台标准版部署指南》中的 Caller-Allowable-Codebase 属性。
-
Entry-Point 属性用于标识允许用作 RIA 入口点的类。使用此属性可以防止从 JAR 文件中的其他可用入口点运行未经授权的代码。有关更多信息,请参阅《Java 平台标准版部署指南》中的入口点属性。
-
Trusted-Only 属性用于防止加载不受信任的组件。有关更多信息,请参阅《Java 平台标准版部署指南》中的“仅可信属性”。
-
Trusted-Library 属性用于允许特权 Java 代码和沙箱 Java 代码之间的调用,而不提示用户授予权限。有关详细信息,请参阅《Java 平台标准版部署指南》中的受信任库属性。
有关将这些属性添加到清单文件的信息,请参阅修改清单文件。
查看 JAR 文件的内容
查看 JAR 文件内容的命令的基本格式是:
jar tf jar-file
让我们看看该命令中使用的选项和参数:
- t 选项表示您要查看 JAR 文件的目录。
- f 选项表示在命令行中指定要查看内容的JAR文件。
- jar-file 参数是您要查看其内容的 JAR 文件的路径和名称。
t 和 f 选项可以按任一顺序出现,但它们之间不能有任何空格。
此命令将把 JAR 文件的目录显示到 stdout 。
提取 JAR 文件的内容
用于提取 JAR 文件内容的基本命令是:
jar xf jar-file [archived-file(s)]
让我们看看这个命令中的选项和参数:
- x 选项表示您要从 JAR 存档中提取文件。
- f 选项指示要从中提取文件的 JAR 文件是在命令行上指定的,而不是通过 stdin 指定的。
- jar-file 参数是要从中提取文件的 JAR 文件的文件名(或路径和文件名)。
- archived-file(s) 是一个可选参数,由要从存档中提取的文件的空格分隔列表组成。如果此参数不存在,Jar 工具将提取存档中的所有文件。
与往常一样, x 和 f 选项在命令中出现的顺序并不重要,但它们之间不能有空格。
提取文件时,Jar 工具会复制所需文件并将其写入当前目录,从而再现文件在存档中的目录结构。原始 JAR 文件保持不变。
注意:提取文件时,Jar 工具将覆盖与提取的文件具有相同路径名的任何现有文件。
更新 JAR 文件
Jar 工具提供了一个 u 选项,您可以使用该选项通过修改其清单或添加文件来更新现有 JAR 文件的内容。
添加文件的基本命令具有以下格式:
jar uf jar-file input-file(s)
在此命令中:
- u 选项表示您想要更新现有的 JAR 文件。
- f 选项表示在命令行上指定要更新的 JAR 文件。
- jar-file 是要更新的现有 JAR 文件。
- input-file(s) 是一个以空格分隔的列表,其中包含要添加到 JAR 文件的一个或多个文件。
存档中已有的任何与要添加的文件具有相同路径名的文件都将被覆盖。
JAR 文件格式提供了许多优势和功能,其中很多是传统的压缩格式如 ZIP 或者 TAR 所没有提供的。它们包括:
- 安全性:可以对 JAR 文件内容加上数字化签名。这样,能够识别签名的工具就可以有选择地为您授予软件安全特权,这是其他文件做不到的,它还可以检测代码是否被篡改过。
- 减少下载时间:如果一个 applet 捆绑到一个 JAR 文件中,那么浏览器就可以在一个 HTTP 事务中下载这个 applet 的类文件和相关的资源,而不是对每一个文件打开一个新连接。
- 压缩:JAR 格式允许您压缩文件以提高存储效率。
- 传输平台扩展。Java 扩展框架 (Java Extensions Framework) 提供了向 Java 核心平台添加功能的方法,这些扩展是用 JAR 文件打包的 (Java 3D 和 JavaMail 就是由 Sun 开发的扩展例子 )。
- 包密封:存储在 JAR 文件中的包可以选择进行 密封,以增强版本一致性和安全性。密封一个包意味着包中的所有类都必须在同一 JAR 文件中找到。
- 包版本控制:一个 JAR 文件可以包含有关它所包含的文件的数据,如厂商和版本信息。
- 可移植性:处理 JAR 文件的机制是 Java 平台核心 API 的标准部分。
聊一聊 JAR 文件和 MANIFEST.MF - 掘金 (juejin.cn)
签署 JAR 文件
您可以使用 JAR 签名和验证工具对 JAR 文件进行签名并为签名添加时间戳。您可以使用 jarsigner 命令调用 JAR 签名和验证工具,因此我们将其简称为“Jarsigner”。
要签署 JAR 文件,您必须首先拥有私钥。私钥及其关联的公钥证书存储在称为密钥库的受密码保护的数据库中。密钥库可以保存许多潜在签名者的密钥。密钥库中的每个密钥都可以通过别名来标识,该别名通常是拥有该密钥的签名者的姓名。例如,属于 Rita Jones 的密钥可能具有别名“rita”。
用于签署 JAR 文件的命令的基本形式是
jarsigner jar-file alias
在此命令中:
- jar-file 是要签名的 JAR 文件的路径名。
- alias 是标识用于签署 JAR 文件的私钥以及该密钥的关联证书的别名。
Jarsigner 工具将提示您输入密钥库和别名的密码。
此命令的基本形式假定要使用的密钥库位于主目录中名为 .keystore 的文件中。它将创建分别名为 x.SF 和 x.DSA 的签名和签名块文件,其中 x 是别名的前八个字母,全部转换为大写。此基本命令将使用签名的 JAR 文件覆盖原始 JAR 文件。
在实践中,您可能想要使用一个或多个可用的命令选项。
例如,鼓励对签名添加时间戳,以便用于部署应用程序的任何工具都可以验证用于签署 JAR 文件的证书在签名文件时是否有效。
如果未包含时间戳,Jarsigner 工具会发出警告。
选项位于 jar-file 路径名之前。下表描述了可用的选项:
选项 | 描述 |
---|---|
-keystore 网址 | 如果您不想使用 .keystore 默认数据库,则指定要使用的密钥库。 |
-sigfile 文件 | 如果您不希望从别名中获取基本名称,请指定 .SF 和 .DSA 文件的基本名称。文件只能由大写字母 (A-Z)、数字 (0-9)、连字符 (-) 和下划线 (_) 组成。 |
-signedjar 文件 | 如果您不希望签名文件覆盖原始未签名文件,则指定要生成的签名 JAR 文件的名称。 |
-tsa 网址 | 使用 URL 标识的时间戳颁发机构 (TSA) 为签名生成时间戳。 |
-tsacert 别名 | 使用由别名标识的 TSA 公钥证书生成签名的时间戳。 |
-altsigner 类 | 指示使用替代签名机制来为签名添加时间戳。完全限定的类名标识所使用的类。 |
-altsignerpath 类路径列表 | 提供由 altsigner 选项标识的类的路径以及该类所依赖的任何 JAR 文件。 |
例子
让我们看几个使用 Jarsigner 工具对 JAR 文件进行签名的示例。在这些示例中,我们将假设以下内容:
- 您的别名是“johndoe”。
- 您要使用的密钥库位于当前工作目录中名为“mykeys”的文件中。
- 您想要用于为签名添加时间戳的 TSA 位于
http://tsa.url.example.com
。
在这些假设下,您可以使用此命令对名为 app.jar 的 JAR 文件进行签名:
jarsigner -keystore mykeys -tsa http://tsa.url.example.com app.jar johndoe
系统将提示您输入密钥库和别名的密码。由于此命令不使用 -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 文件将保持不变。此外,签名将带有标识为 testalias 的 TSA 公钥证书的时间戳。
附加信息
JAR 签名和验证工具的完整参考页已上线:安全工具摘要
注意:当证书是自签名时, UNKNOWN 将显示为应用程序的发布者。有关详细信息,请参阅运行列为 UNKNOWN 的发布者的应用程序是否安全?。
验证签名的 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 文件的内容自签名以来已更改,则在您尝试验证该文件时将出现类似于以下内容的消息:
jarsigner: java.lang.SecurityException: invalid SHA1
signature file digest for test/classes/Manifest.class
注意:如果签名的 JAR 文件使用
java.home/lib/security/java.security
文件中的 jdk.jar.disabledAlgorithms
安全属性中指定的任何算法(其中 java.home
是安装 JRE 的目录)。了解签名和验证
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 扩展名,表明它们是由默认数字签名算法创建的。如果使用与某些其他标准算法关联的密钥进行签名,则其他文件扩展名也是可能的。
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);
正如我们在上一节中看到的,JarRunner 通过 JarClassLoader 访问 JAR 处理 API。
传递给 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");
}
关键语句以粗体突出显示。其他语句用于错误处理。
一旦 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。因此,要传递给该应用程序的任何参数都位于元素 1 中,并在 args 数组中。 JarRunner 获取这些元素,并创建一个名为 newArgs 的新数组以传递给应用程序(上面的粗线)。 JarRunner 然后将入口点的类名和新参数列表传递给 JarClassLoader 的 invokeClass 方法。正如我们在上一节中看到的, invokeClass 将加载应用程序的入口点类,向其传递任何参数,然后启动应用程序。
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 处理功能:
JarURLConnection 类和 JAR URL
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。
java.net.JarURLConnection 类
此类表示应用程序和 JAR 文件之间的通信链接。它具有访问 JAR 文件清单的方法。 getMainClassName 的第二行是:
JarURLConnection uc = (JarURLConnection)u.openConnection();
在此语句中,第一行中创建的 URL 实例打开 URLConnection 。然后, URLConnection 实例被转换为 JarURLConnection ,以便它可以利用 JarURLConnection 的 JAR 处理功能。
获取清单属性:java.util.jar.Attributes
在 JarURLConnection 打开 JAR 文件后,您可以使用 JarURLConnection 的 getMainAttributes 方法访问 JAR 文件清单中的标头信息。此方法返回 java.util.jar.Attributes 的实例,该类将 JAR 文件清单中的标头名称与其关联的字符串值进行映射。 getMainClassName 中的第三行创建一个 Attributes 对象:
Attributes attr = uc.getMainAttributes();
要获取清单的 Main-Class 标头的值, 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 用于指定其他标准清单标头。)
invokeClass 方法
我们已经了解了 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 将用于将参数传递给该类并启动它。您可以参考 The Reflection API 教程来回顾反射。
-
问题:如何调用打包为 JAR 文件的小程序?
答案:要调用打包为 JAR 文件的小程序,请打开包含该小程序的页面:
<applet code=AppletClassName.class archive="JarFileName.jar" width=320 height=240> </applet>
-
问题:
jar
命令中的-e
选项的用途是什么?答案:此选项自 Java SE 6 起可用。它将入口点设置为捆绑到可执行 jar 文件中的独立应用程序的应用程序入口点。使用此选项会创建或覆盖清单文件中的 Main-Class 属性值。
可以在创建 jar 文件或更新 jar 文件期间使用此选项。此选项指定应用程序入口点,而无需编辑或创建清单文件。
例如,此命令创建 Main.jar,其中清单中的 Main-Class 属性值设置为 Main:jar cfe Main.jar Main Main.class
-
问题:JAR 文件中的清单有何意义?
答案:JAR 文件的清单提供有关 JAR 文件其他内容的元信息。清单本身驻留在 META-INF/MANIFEST.mf 中。元信息可以包括
- 对其他 jar 文件的依赖
- 调用“java -jar file.jar”时运行的类的名称
- 版本控制信息
- 安全信息
-
问题:如何修改 JAR 的清单文件?
答:通常,修改默认清单涉及向清单添加特殊用途的标头,以允许 JAR 文件执行特定的所需功能。
要修改清单,您必须首先准备一个包含完整且有效的清单文件的文本文件。然后,您可以使用 JAR 工具的
m
选项将文件中的信息添加到清单中。您准备的清单文件必须以换行符或回车符结尾。如果最后一行不以换行符或回车符结尾,则将无法正确解析它。
启动策略工具
要启动策略工具,只需在命令行中键入以下命令:
policytool
这将打开策略工具窗口。
每当启动策略工具时,它都会尝试使用用户策略文件中的策略信息填充此窗口。默认情况下,用户策略文件在您的主目录中名为 .java.policy
。如果策略工具找不到用户策略文件,则会发出警告并显示空白的策略工具窗口(带有标题和按钮但没有数据的窗口),如下图所示。
![a blank Policy Tool window](https://docs.oracle.com/javase/tutorial/figures/security/ptBlank1.gif)
然后,您可以继续打开现有策略文件或创建新策略文件。
第一次运行策略工具时,您将看到空白的策略工具窗口,因为用户策略文件尚不存在。您可以立即继续创建新的策略文件,如下一步所述。
授予所需的权限
要创建新条目,请单击策略工具主窗口中的添加策略条目按钮。这将显示“策略条目”对话框,如下图所示。
![the Policy Entry dialog](https://docs.oracle.com/javase/tutorial/figures/security/AddEntryBlank1.gif)
策略条目指定对来自特定代码源的代码的一个或多个权限 - 来自特定位置 (URL) 的代码、由特定实体签名的代码或两者。
CodeBase 和 SignedBy 文本框指定您想要授予将添加到文件中的权限的代码。
- CodeBase值表示代码源位置;您授予从该位置进行编码的权限。空的 CodeBase 条目表示“任何代码”——代码源自何处并不重要。
- SignedBy 值指示存储在密钥库中的证书的别名。该证书中的公钥用于验证代码上的数字签名。
您向由与别名指定的密钥库条目中的公钥对应的私钥签名的代码授予权限。 SignedBy 条目是可选的;省略它表示“任何签名者”——代码是否签名或由谁签名并不重要。
如果您同时拥有 CodeBase 和 SignedBy 条目,则仅向来自指定位置且由指定别名签名的代码授予权限。
您可以向存储示例的位置 (URL) 中的所有代码授予权限。
在“策略输入”对话框的“代码库”文本框中键入以下 URL:
https://docs.oracle.com/javase/tutorial/security/tour1/examples/
注意:这是一个网址。因此,它必须始终使用斜杠作为分隔符,而不是反斜杠。
将“签名者”文本框留空,因为您不需要对代码进行签名。
注意:要不仅向先前指定的目录中的任何代码(
.class
文件)授予权限,而且还向 security
目录及其子目录中的任何代码( .class
文件)授予权限,请在 CodeBase 框中键入以下 URL:
https://docs.oracle.com/javase/tutorial/security/
您已指定代码的来源(CodeBase),并且代码不必签名(因为没有 SignedBy 值)。
您现在已经指定了该策略条目,因此单击“策略条目”对话框中的“完成”按钮。策略工具窗口现在包含一行代表策略条目,显示 CodeBase
值。
![the PolicyTool window, showing the new policy entry](https://docs.oracle.com/javase/tutorial/figures/security/WQ1ptOneCB1.gif)
注意:我们将在下一课中授予此新策略条目的权限。
保存策略文件
要保存您创建的新策略文件,请从“文件”菜单中选择“另存为”命令。这将显示“另存为”对话框。
控制应用程序快速浏览课程中的示例假设您将策略文件存储在 C:
驱动器上的 Test
目录中。
导航到 Test
目录。输入文件名 examplepolicy
并单击“保存”。
策略文件现已保存,其名称和路径显示在标记为 Policy File
的文本框中。
![the PolicyTool window showing the policy file](https://docs.oracle.com/javase/tutorial/figures/security/WQ1ptWithFilename1.gif)
通过从“文件”菜单中选择“退出”退出策略工具。
代码和文档安全
如果您以电子方式向某人发送重要文档(或多个文档),或者要运行的小程序或应用程序,收件人需要一种方法来验证该文档或代码来自您并且在传输过程中未被修改(例如,被恶意用户修改)拦截它)。
数字签名、证书和密钥库都有助于确保您发送的文件的安全。
数字签名
使用数字签名的基本思想如下。
- 您使用您的私钥之一“签署”文档或代码,您可以使用
keytool
或安全 API 方法生成该私钥。也就是说,您使用jarsigner
工具或安全 API 方法为文档或代码生成数字签名。 - 您将签名的文档发送给收件人。
- 您还向收件人提供您的公钥。该公钥与您最初用于生成签名的私钥相对应。
- 您的收件人使用您的公钥来验证您的文档是否来自您,并且在到达他/她之前没有被修改。
收件人需要先确保您的公钥本身是真实的,然后才能使用它来验证您的签名的真实性。因此,您通常会提供一个证书,其中包含您的公钥以及可以保证您的密钥真实性的证书颁发机构的密钥。有关详细信息,请参阅下一节。
有关签名和验证的术语和概念的更多信息,以及对其优点的进一步说明,请参阅“JAR 文件中的打包程序”课程的签名 JAR 文件部分。
证书
证书包含:
- 公钥。
- 其证书所在的实体(个人、公司等)的“专有名称”信息。该实体称为证书主体或所有者。专有名称信息包括以下属性(或子集):实体的名称、组织单位、组织、城市或地区、州或省以及国家/地区代码。
- 数字签名。证书由一个实体(颁发者)签名,以保证所附公钥是另一个实体(所有者)的实际公钥。
- 签名者(发行者)的专有名称信息。
接收者检查证书是否有效的一种方法是使用其颁发者(签名者)的公钥验证其数字签名。
该密钥本身可以存储在另一个证书中,该证书的签名也可以使用下一个证书颁发者的公钥进行验证,并且该密钥也可以存储在另一个证书中,依此类推。当您到达您已经信任的公钥时,您可以停止检查并使用它来验证相应证书上的签名。
如果接收者无法建立信任链,则他/她可以使用 keytool
-import
或 -printcert
命令计算证书指纹。指纹是一个相对较短的数字,可以唯一且可靠地标识证书。
(从技术上讲,指纹是使用消息摘要功能的证书信息的哈希值。)然后,接收者可以致电证书所有者,并将收到的证书的指纹值与发送的证书进行比较。
如果指纹相同,则证书相同。
因此,您可以确保证书在传输过程中没有被修改。使用证书时的另一个潜在不确定性是发送者的身份。有时证书是自签名的,即使用证书中公钥对应的私钥进行签名;发行人与主体相同。
自签名证书对于开发和测试应用程序非常有用。但是,在部署给用户之前,请从受信任的第三方(称为证书颁发机构 (CA))获取证书。
为此,您需要向 CA 发送自签名证书签名请求 (CSR)。 CA 可能会通过检查您的驾驶执照或其他信息来验证 CSR 上的签名和您的身份。
然后,CA 通过颁发证书并使用自己(CA 的)私钥对其进行签名来保证您是公钥的所有者。任何信任颁发 CA 公钥的人现在都可以验证证书上的签名。
在许多情况下,颁发 CA 本身可能拥有来自 CA 层次结构中更高层 CA 的证书,从而形成证书链。
您信任的实体的证书通常会作为“受信任的证书”导入到您的密钥库中。然后,每个这样的证书中的公钥可以用于验证使用对应的私钥生成的签名。此类验证可以通过以下方式完成:
jarsigner
工具(如果文档/代码和签名出现在 JAR 文件中),- API 方法,或
- 运行时系统,当尝试进行资源访问并且策略文件指定如果其签名是可信的则允许尝试访问的代码进行资源访问。代码的类文件和签名必须位于 JAR 文件中。
如果您要将签名的代码或文档发送给其他人,则需要向他们提供包含与用于签署代码/文档的私钥相对应的公钥的证书。 keytool
-export
命令或 API 方法可以将您的证书从密钥库导出到文件,然后可以将其发送给需要它的任何人。接收证书的人可以使用 API 方法或 keytool
-import
命令将其作为可信证书导入密钥库。
如果您使用 jarsigner
工具为 JAR 文件生成签名,该工具将从您的密钥库中检索您的证书及其支持证书链。然后,该工具将它们与签名一起存储在 JAR 文件中。
密钥库
私钥及其关联的公钥证书存储在称为密钥库的受密码保护的数据库中。密钥库可以包含两种类型的条目:上面讨论的可信证书条目和密钥/证书条目,每个条目都包含私钥和相应的公钥证书。密钥库中的每个条目均由别名标识。
密钥库所有者可以在密钥库中拥有多个密钥,可以通过不同的别名进行访问。别名通常以密钥库所有者使用关联密钥的特定角色命名。别名还可以标识密钥的用途。例如,别名 signPersonalEmail
可用于标识其私钥用于签署个人电子邮件的密钥库条目,而别名 signJarFiles
可用于标识其私钥用于签名的密钥库条目。私钥用于签署 JAR 文件。
keytool
工具可用于
- 创建私钥及其关联的公钥证书
- 发出证书请求,并将其发送给相应的证书颁发机构
- 导入证书回复,从您联系的证书颁发机构获得
- 导入属于其他方的公钥证书作为可信证书
- 管理您的密钥库
API 方法还可用于访问和修改密钥库。
工具和 API 注释
请注意以下有关使用与数字签名相关的工具和 API 的事项。
- 您可以使用 JDK 安全 API、工具或组合来生成密钥和签名以及导入证书。您可以使用这些 API 或工具功能与他人安全地交换文档。
- 要使用这些工具进行文档交换,文档必须放置在 JAR (Java ARchive) 文件中,该文件可以由
jar
工具创建。 JAR 文件是将多个文件封装在一个位置的好方法。当文件被“签名”时,生成的数字签名字节需要存储在某处。当 JAR 文件被签名时,签名可以进入 JAR 文件本身。这就是当您使用jarsigner
工具对 JAR 文件进行签名时会发生的情况。 - 如果您正在创建要签名的小程序代码,则需要将其放置在 JAR 文件中。如果您正在创建可能通过使用安全管理器运行而受到类似限制的应用程序代码,情况也是如此。
您需要 JAR 文件的原因是,当策略文件指定允许由特定实体签名的代码进行一项或多项操作(例如特定文件读取或写入)时,该代码应来自签名的 JAR 文件。
(术语“签名代码”是“类文件中出现在已签名的 JAR 文件中的代码”的缩写。) - 为了使运行时系统检查代码签名,将运行代码的个人/组织首先需要将证书导入到其密钥库中,该证书验证与用于签署代码的私钥相对应的公钥。
- 为了让
jarsigner
工具验证 JAR 文件签名的真实性,接收 JAR 文件的个人/组织首先需要将证书导入到其密钥库中,该证书验证与所使用的私钥相对应的公钥签署代码。 - 目前还没有用于证书创建的 API。
使用 JDK 安全 API 签署文档
生成和验证签名向您展示如何使用 JDK Security API 来签署文档。本课程展示了由拥有原始文档的人执行的一个程序会执行以下操作:
- 生成密钥,
- 使用私钥为数据生成数字签名,并且
- 将公钥和签名导出到文件。
然后它显示了另一个程序的示例,由数据、签名和公钥的接收者执行。它显示了程序如何
- 导入公钥
- 验证签名的真实性。
本课程还向您展示导入和提供密钥(包括证书)的其他方法。
使用工具签署代码或文档
签署代码并授予其权限课程展示了如何使用 Java 安全工具将代码放入 JAR 文件中、对其进行签名并导出公钥。
然后,它展示了您的接收者如何使用这些相同的 Java 工具导入您的公钥证书,然后向策略文件添加一个条目,该条目将授予您的代码访问接收者控制的系统资源所需的权限。
交换文件课程将介绍如何使用 Java 安全工具对文档进行签名,然后使用 keytool
导出公钥的公钥证书。对应于使用 keytool
签署该文档的私钥。然后,它显示您的收件人如何通过安装您的公钥证书来验证您的签名,然后使用 jarsigner
工具验证您的签名。
这两个教训有很多共同点。在这两种情况下,代码或文档发送者的前两个步骤是:
- 使用
jar
工具创建包含文档或类文件的 JAR 文件。 - 使用
keytool
-genkey
命令生成密钥(如果它们尚不存在)。
接下来的两个步骤是可选的:
- 使用
keytool
-certreq
命令;然后将生成的证书签名请求发送给证书颁发机构 (CA),例如 VeriSign。 - 使用
keytool
-import
命令导入 CA 的响应。
需要执行接下来的两个步骤:
- 使用
jarsigner
工具和之前生成的私钥对 JAR 文件进行签名。 - 使用
keytool
-export
命令导出公钥证书。然后将签名的 JAR 文件和证书提供给接收者。
在这两种情况下,签名 JAR 文件和证书的接收者都应使用 keytool
-import
命令将证书导入为可信证书。 keytool
将尝试构建从要导入的证书到密钥库中已受信任的证书的信任链。如果失败, keytool
将显示证书指纹并提示您验证它。
如果发送的是代码,接收方还需要修改策略文件,以允许所需的资源访问由与导入证书中的公钥相对应的私钥签名的代码。策略工具可用于执行此操作。
如果发送的是一份或多份文档,接收方需要使用 jarsigner
工具验证 JAR 文件签名的真实性。
本课程讨论两个可选步骤。接下来的两节课(对代码进行签名、授予权限和交换文件)将介绍其他步骤。
生成公钥证书的证书签名请求 (CSR)
当使用 keytool
生成公钥/私钥对时,它会创建一个包含私钥和公钥的自签名证书的密钥库条目。
(也就是说,证书是使用相应的私钥签名的。)这在开发和测试应用程序时就足够了。
但是,如果证书由证书颁发机构 (CA) 签名,则更可能受到其他人的信任。要获取 CA 签名的证书,您首先需要通过如下命令生成证书签名请求 (CSR):
keytool -certreq -alias alias -file csrFile
这里的 alias 用于访问包含私钥和公钥证书的密钥库条目,csrFile 指定用于此命令创建的 CSR 的名称。
然后,您将此文件提交给 CA,例如 VeriSign, Inc.。CA 对您(请求者)(“主题”)进行身份验证,然后签署并返回验证您的公钥的证书。通过签署证书,CA 保证您是公钥的所有者。
在某些情况下,CA 将返回证书链,每个证书都验证链中前一个证书的签名者的公钥。
导入来自 CA 的响应
向证书颁发机构 (CA) 提交证书签名请求 (CSR) 后,您需要通过导入 CA 返回给您的证书(或证书链),将密钥库中的原始自签名证书替换为证书链。
但首先您需要在密钥库(或 cacerts
密钥库文件中,如下所述)中有一个“可信证书”条目来验证 CA 的公钥。通过这样的条目,可以验证 CA 的签名。也就是说,可以验证 CA 在证书上的签名,或者 CA 在响应您的 CSR 时发送给您的链中的最终证书上的签名。
从 CA 导入证书作为“受信任的证书”
在导入来自 CA 的证书回复之前,您的密钥库或 cacerts
文件中需要一个或多个“可信证书”。
- 如果证书回复是证书链,则您只需要链的顶部证书 - 验证该 CA 公钥的“根”CA 证书。
- 如果证书回复是单个证书,则您需要颁发 CA(签署该证书的 CA)的证书。如果该证书不是自签名的,则您需要其签名者的证书,依此类推,直至自签名的“根”CA 证书。
cacerts
文件代表具有 CA 证书的系统范围密钥库。该文件位于 JRE 安全属性目录 java.home/lib/security
中,其中 java.home 是 JRE 安装目录。
重要提示:验证您的 cacerts
文件
由于您信任 cacerts
文件中的 CA 作为向其他实体签名和颁发证书的实体,因此您必须仔细管理 cacerts
文件。 cacerts
文件应仅包含您信任的 CA 的证书。您有责任验证 cacerts
文件中捆绑的受信任根 CA 证书并做出自己的信任决定。要从 cacerts
文件中删除不受信任的 CA 证书,请使用 keytool
命令的删除选项。您可以在JRE安装目录中找到 cacerts
文件。如果您无权编辑此文件,请联系您的系统管理员。
cacerts
文件包含许多受信任的 CA 证书。如果您将 CSR 发送给这些受信任的供应商之一(例如 VeriSign),则无需将该供应商的根证书作为受信任的证书导入到您的密钥库中;您可以继续下一节,了解如何导入来自 CA 的证书回复。
来自 CA 的证书通常是自签名的或由另一个 CA 签名的,在这种情况下,您还需要一个证书来验证该 CA 的公钥。假设 ABC, Inc. 公司是一家 CA,并且您获得了一个名为 ABCCA.cer
的文件,据称是来自 ABC 的自签名证书,用于验证该 CA 的公钥。
在将证书作为“受信任”证书导入之前,请务必小心确保其有效!首先查看它(使用 keytool
-printcert
命令或不带 -noprompt
选项的 keytool
-import
命令),并确保显示的证书指纹与预期的匹配。您可以致电发送证书的人,并将您看到的指纹与他们显示的指纹或安全公钥存储库显示的指纹进行比较。
只有指纹相同才能保证证书在传输过程中不会被其他人(例如攻击者)的证书替换。
如果发生此类攻击,并且您在导入证书之前没有检查证书,那么您最终会信任攻击者签署的任何内容。
如果您相信证书有效,则可以通过如下命令将其添加到密钥库中:
keytool -import -alias alias -file ABCCA.cer -keystore storefile
此命令在密钥库中创建一个“受信任的证书”条目,其名称是 storefile 中指定的名称。该条目包含文件 ABCCA.cer
中的数据,并为其分配了指定的别名。
导入来自 CA 的证书回复
一旦您导入了所需的受信任证书(如上一节所述),或者它们已存在于您的密钥库或 cacerts
文件中,您就可以导入证书回复,从而替换您自己的证书-带有证书链的签名证书。
该链可以是 CA 响应您的请求而返回的链(如果 CA 回复是一条链),也可以是使用证书回复和已可用的受信任证书构建的链(如果 CA 回复是单个证书)在密钥库或 cacerts
密钥库文件中。
例如,假设您将证书签名请求发送给 VeriSign。然后,您可以通过以下方式导入回复,其中假设返回的证书位于 certReplyFile 指定的文件中:
keytool -import -trustcacerts
-keystore storefile
-alias alias
-file certReplyFile
在一行中键入此命令。
证书回复通过使用密钥库中的受信任证书进行验证,也可以选择使用 cacerts
密钥库文件中配置的证书(如果指定了 -trustcacerts
选项)。使用链中更高级别的证书来验证链中的每个证书。您只需信任链中的顶级“根”CA 证书。如果您尚未信任顶级证书, keytool
将显示该证书的指纹并询问您是否要信任它。
指定(按别名)条目的新证书链将替换与此条目关联的旧证书(或链)。仅当提供有效的密钥密码(用于保护条目私钥的密码)时,才能替换旧链。如果未提供密码并且私钥密码与密钥库密码不同,则会提示用户输入密码。
有关生成 CSR 和导入证书回复的更多详细信息,请参阅 keytool
文档:
创建包含类文件的 JAR 文件
接下来,创建一个包含 Count.class
文件的 JAR 文件。在命令窗口中键入以下内容:
jar cvf Count.jar Count.class
这将创建一个 JAR 文件 Count.jar
,并将 Count.class
文件放入其中。
标准 JDK 工具和实用程序
- 基本工具(appletviewer、extcheck、jar、java、javac、javadoc、javah、javap、jdb、jdeps)
- 安全工具(keytool、jarsigner、policytool、kinit、klist、ktab)
- 国际化工具 (native2ascii)
- 远程方法调用 (RMI) 工具(rmic、rmiregistry、rmid、serialver)
- Java IDL 和 RMI-IIOP 工具(tnameserv、idlj、orbd、servertool)
- Java 部署工具(javapackager、pack200、unpack200)
- Java Web 启动工具 (javaws)
- Java 故障排除、分析、监控和管理工具(jcmd、jconsole、jmc、jvisualvm)
- Java Web 服务工具(schemagen、wsgen、wsimport、xjc)
JDK开发工具 --- JDK Development Tools (oracle.com)
X.509 证书 --- X.509 certificates (oracle.com)
Java SE 安全性 --- Java SE Security (oracle.com)
Java 加密体系结构 (JCA) 参考指南 --- Java Cryptography Architecture (JCA) Reference Guide (oracle.com)
-jar参数运行应用时,设置classpath的方法
转自https://blog.csdn.net/sayyy/article/details/81120749
前言
- 期望在java -jar命令执行java程序时,能够指定classpath
分析
因为使用“-jar”选项(形如:java -jar xxx.jar )来运行一个可执行的jar包时,jar包会覆“-cp”的值。
换句话说,-jar 后面所跟的jar包的优先级别最高。如果指定了-jar选项,所有环境变量和命令行制定的搜索路径都将被忽略。JVM APPClassloader将只会以jar包为搜索范围.
解决方案
Java 命令行提供了如何扩展bootStrap 级别class的简单方法.
- -Xbootclasspath: 完全取代基本核心的Java class 搜索路径.不常用,否则要重新写所有Java 核心class
- -Xbootclasspath/a: 后缀。将classpath添加在核心class搜索路径后面。常用!!
- -Xbootclasspath/p: 前缀。将classpath添加在核心class搜索路径前面.不常用,避免引起不必要的冲突.
示例如下: (分隔符与classpath参数类似,unix使用:号,windows使用;号,这里以unix为例)
-
//特定的jar到classpath
-
java -Xbootclasspath/a:/usrhome/thirdlib1.jar:/usrhome/thirdlib2.jar -jar yourJarExe.jar
-
//添加目录到classpath
-
java -Xbootclasspath/a:/usrhome/thirdlib1/:/usrhome/thirdlib1/ -jar yourJarExe.jar
-
//添加当前目录(可执行的jar所在的目录)到classpath
-
java -Xbootclasspath/a:./ -jar yourJarExe.jar
扩展知识(转)
自JDK 1.2以后,JVM采用了委托(delegate)模式来载入class.采用这种设计的原因可以参考http://java.sun.com/docs/books/tutorial/ext/basics/load.html
归纳来讲:是基于JVM sandbox(沙盒)安装模型上提供应用层的可定制的安全机制.
Java虚拟机(JVM)寻找Class的顺序
1. Bootstrap classes
属于Java 平台核心的class,比如java.lang.String等.及rt.jar等重要的核心级别的class.这是由JVM Bootstrap class loader来载入的.一般是放置在{java_home}\jre\lib目录下
2. Extension classes
基于Java扩展机制,用来扩展Java核心功能模块.比如Java串口通讯模块comm.jar.一般放置在{Java_home}\jre\lib\ext目录下
3. User classes
开发人员或其他第三方开发的Java程序包.通过命令行的-classpath或-cp,或者通过设置CLASSPATH环境变量来引用.JVM通过放置在{java_home}\lib\tools.jar来寻找和调用用户级的class.常用的javac也是通过调用tools.jar来寻找用户指定的路径来编译Java源程序.这样就引出了User class路径搜索的顺序或优先级别的问题.
- 3.1 缺省值:调用Java或javawa的当前路径(.),是开发的class所存在的当前目录
- 3.2 CLASSPATH环境变量设置的路径.如果设置了CLASSPATH,则CLASSPATH的值会覆盖缺省值
- 3.3 执行Java的命令行-classpath或-cp的值,如果制定了这两个命令行参数之一,它的值会覆盖环境变量CLASSPATH的值
- 3.4 -jar 选项:如果通过java -jar 来运行一个可执行的jar包,这当前jar包会覆盖上面所有的值.换句话说,-jar 后面所跟的jar包的优先级别最高,如果指定了-jar选项,所有环境变量和命令行制定的搜索路径都将被忽略.JVM APPClassloader将只会以jar包为搜索范围.
有关可执行jar有许多相关的安全方面的描述,可以参考http://java.sun.com/docs/books/tutorial/jar/ 来全面了解.
这也是为什么应用程序打包成可执行的jar包后,不管你怎么设置classpath都不能引用到第三方jar包的东西了.
-jar参数运行应用时,设置classpath的方法_-jar 覆盖classpath-CSDN博客
了解扩展类加载
扩展框架利用类加载委托机制。当运行时环境需要为应用程序加载新类时,它会按顺序在以下位置查找该类:
- 引导类: rt.jar 中的运行时类、 i18n.jar 中的国际化类等。
- 安装的扩展:JRE 的 lib/ext 目录中的 JAR 文件中的类以及系统范围的、特定于平台的扩展目录(例如 Solaris™ 操作系统上的 /usr/jdk/packages/lib/ext )但请注意,此目录的使用仅适用于 Java™ 6 及更高版本)。
- 类路径:系统属性 java.class.path 指定的路径上的类,包括 JAR 文件中的类。如果类路径上的 JAR 文件具有带有
Class-Path
属性的清单,则还将搜索由Class-Path
属性指定的 JAR 文件。默认情况下,java.class.path
属性的值为.
,即当前目录。您可以使用 -classpath 或 -cp 命令行选项或设置CLASSPATH
环境变量来更改该值。命令行选项会覆盖CLASSPATH
环境变量的设置。
例如,优先级列表告诉您,仅当在 rt.jar 、 i18n.jar 或已安装的扩展中的类中未找到要加载的类时,才会搜索类路径。
除非您的软件出于特殊目的实例化自己的类加载器,否则您实际上不需要了解更多信息,只需牢记此优先级列表即可。特别是,您应该注意可能存在的任何类名冲突。
例如,如果您在类路径上列出一个类,如果运行时环境改为加载在已安装的扩展中找到的同名的另一个类,您将得到意外的结果。
Java 类加载机制
Java 平台使用委托模型来加载类。基本思想是每个类加载器都有一个“父”类加载器。
加载类时,类加载器首先将类的搜索“委托”给其父类加载器,然后再尝试查找类本身。
以下是类加载 API 的一些亮点:
- java.lang.ClassLoader 及其子类中的构造函数允许您在实例化新的类加载器时指定父类。如果您没有显式指定父级,则虚拟机的系统类加载器将被指定为默认父级。
- ClassLoader 中的 loadClass 方法在调用加载类时按顺序执行这些任务:
- 如果一个类已经被加载,它就会返回它。
- 否则,它将对新类的搜索委托给父类加载器。
- 如果父类加载器没有找到该类,则 loadClass 调用方法 findClass 来查找并加载该类。
- 如果父类加载器未找到该类,则 ClassLoader 的 findClass 方法会在当前类加载器中搜索该类。当您在应用程序中实例化类加载器子类时,您可能希望重写此方法。
- 类 java.net.URLClassLoader 作为扩展和其他 JAR 文件的基本类加载器,重写 java.lang.ClassLoader 的 findClass 方法来搜索一个或多个指定的 URL 中的类并资源。
要查看使用与 JAR 文件相关的某些 API 的示例应用程序,请参阅本教程中的使用 JAR 相关 API 课程。
类加载和 java 命令
Java平台的类加载机制体现在 java 命令中。
- 在 java 工具中, -classpath 选项是设置 java.class.path 属性的简写方式。
- -cp 和 -classpath 选项是等效的。
- -jar 选项运行打包在 JAR 文件中的应用程序。有关此选项的说明和示例,请参阅本教程中的“运行 JAR 打包的软件”课程。
安装的扩展
安装的扩展是 Java 运行时环境 (JRE™) 软件的 lib/ext 目录中的 JAR 文件。顾名思义,JRE 是 Java 开发工具包的运行时部分,包含平台的核心 API,但不包含编译器和调试器等开发工具。
JRE 可以单独使用,也可以作为 Java 开发工具包的一部分使用。
JRE 是 JDK 软件的严格子集。 JDK 软件目录树的子集如下所示:
![JDK software directory tree](https://docs.oracle.com/javase/tutorial/figures/ext/extb1.gif)
JRE 由图中突出显示的框中的那些目录组成。无论您的 JRE 是独立的还是 JDK 软件的一部分,JRE 目录的 lib/ext 中的任何 JAR 文件都会自动被运行时环境视为扩展。
由于安装的扩展扩展了平台的核心 API,因此请谨慎使用它们。它们很少适合单个或一小组应用程序使用的接口。
此外,由于安装的扩展定义的符号在所有 Java 进程中都是可见的,因此应注意确保所有可见符号都遵循适当的“反向域名”和“类层次结构”约定。例如, com.mycompany.MyClass 。
从 Java 6 开始,扩展 JAR 文件也可以放置在独立于任何特定 JRE 的位置,以便扩展可以由系统上安装的所有 JRE 共享。在 Java 6 之前, java.ext.dirs 的值引用单个目录,但从 Java 6 开始,它是一个目录列表(如 CLASSPATH ),指定扩展所在的位置搜索。路径的第一个元素始终是 JRE 的 lib/ext 目录。第二个元素是 JRE 外部的目录。这个其他位置允许扩展 JAR 文件安装一次并由该系统上安装的多个 JRE 使用。该位置因操作系统而异:
- Solaris™ 操作系统: /usr/jdk/packages/lib/ext
- Linux: /usr/java/packages/lib/ext
- 微软Windows: %SystemRoot%\Sun\Java\lib\ext
请注意,放置在上述目录之一中的已安装扩展扩展了该系统上每个 JRE(Java 6 或更高版本)的平台。
一个简单的例子
让我们创建一个简单的安装扩展。我们的扩展由一个类 RectangleArea 组成,它计算矩形的面积:
public final class RectangleArea {
public static int area(java.awt.Rectangle r) {
return r.width * r.height;
}
}
该类有一个方法 area ,它接受 java.awt.Rectangle 的实例并返回矩形的面积。
假设您想使用名为 AreaApp
的应用程序测试 RectangleArea :
import java.awt.*;
public class AreaApp {
public static void main(String[] args) {
int width = 10;
int height = 5;
Rectangle r = new Rectangle(width, height);
System.out.println("The rectangle's area is "
+ RectangleArea.area(r));
}
}
此应用程序实例化一个 10 x 5 矩形,然后使用 RectangleArea.area 方法打印出该矩形的面积。
在没有扩展机制的情况下运行AreaApp
我们首先回顾一下如何在不使用扩展机制的情况下运行 AreaApp
应用程序。我们假设 RectangleArea 类捆绑在名为 area.jar 的 JAR 文件中。
当然, RectangleArea 类不是Java平台的一部分,因此您需要将 area.jar 文件放在类路径上才能运行 AreaApp
没有得到运行时异常。例如,如果 area.jar 位于目录 /home/user 中,您可以使用以下命令:
java -classpath .:/home/user/area.jar AreaApp
此命令中指定的类路径包含当前目录(包含 AreaApp.class )和包含 RectangleArea 包的 JAR 文件的路径。通过运行以下命令,您将获得所需的输出:
The rectangle's area is 50
使用扩展机制运行AreaApp
现在让我们看看如何使用 RectangleArea 类作为扩展来运行 AreaApp
。
要使 RectangleArea 类成为扩展,请将文件 area.jar 放置在 JRE 的 lib/ext 目录中。这样做会自动赋予 RectangleArea 已安装扩展的状态。
将 area.jar 作为扩展安装后,您可以运行 AreaApp
而无需指定类路径:
java AreaApp
由于您使用 area.jar 作为已安装的扩展,因此运行时环境将能够找到并加载 RectangleArea
类,即使您尚未在类路径中指定它也是如此。同样,系统上任何用户运行的任何小程序或应用程序都可以找到并使用 RectangleArea 类。
如果系统上安装了多个 JRE(Java 6 或更高版本)并希望 RectangleArea 类可用作所有 JRE 的扩展,而不是将其安装在 lib/ext 中特定 JRE 的目录,将其安装在系统范围的位置。例如,在运行 Linux 的系统上,将 area.jar 安装在目录 /usr/java/packages/lib/ext 中。然后, AreaApp 可以使用该系统上安装的不同 JRE 运行,例如,如果不同的浏览器配置为使用不同的 JRE。
以下是执行独立 java 程序的一些基本命令。
我从事 Java 开发人员已经快 5 年了,但我很少从命令行执行 Java 程序,而且不太熟练。我只是想我会在这里为自己和其他人记下它。
编译java类
javac -cp /project/classes -d /project/classes /path/to/File.java
-cp -> 类路径 - 其他已编译依赖项所在的路径。
-d -> 操作生成的 .class(字节码)文件的目标。
执行程序
java -cp /project/classes ClassFileName(or package.ClassFileName)
arg1 arg2 ...
如果类路径包含子文件夹结构,则需要 package.ClassFileName。
从 Java 11 开始,您还可以指定单个源文件,并且可以在单个命令中完成编译和执行。
这不会生成 .class 文件。
仅当源文件包含所有源并且不需要其他编译的类来运行时,这才有效。例如。一个典型的 Hello world 场景
java /path/to/package/ClassFileName arg1 arg2 ...
创建javadoc
javadoc -d /documentation/path
-sourcepath /path/to/java/files
-subpackages /path/to/root/package
什么是类路径?
类路径是Java中的基本参数之一,但它经常被编程新手误解。简单来说,类路径只是一组路径,Java 编译器和 JVM 必须沿着这些路径找到编译或执行其他类所需的类。
Classpath 如何帮助 JVM 执行类文件
让我们从一个例子开始。假设我们有一个 Main.java 文件,位于 /Users/vikram/Documents/test-java/src/com/programming/v1/Main.java 文件夹中。假设我们在 /Users/vikram/Documents 中并想要编译这个类:现在,要执行这个类文件,我们需要使用类路径或 cp 标志告诉 Java 虚拟机在哪里查找 .class 文件。 java命令。第一个参数是写入包的根文件夹。第二个参数是包名和类名。当执行Java命令时,Java虚拟机会查找test-java/src文件夹,然后加载主类来执行它。
Java 中的不变性
来源:Medium Java 中的变量有两种类型:原始变量和引用变量。 Java 中的所有内容都是按值传递的,但在引用类型的情况下,可以使用传递的内存地址来更新源数据。 Final关键字用于使变量充当常量,即避免重新赋值。这对于没有堆内存的基元非常有效,而对于引用类型,仅重新分配受到限制并且内部状态可以更改。
这可能会导致许多并发问题和竞争条件。因此,在 Java 的常规类型中包含不可变特性提供了许多好处。
Java 中不变性的好处
1. 线程安全
不可变类型不受多线程环境中竞争条件的影响,因为对象在创建后将保持一致。多个线程无法更改其内部状态,因此不需要同步。
2. 基本型
Java 标准库中的 String 是基类的一个很好的例子。这是一个非常简单且不可变的类,可用于在其之上构建业务逻辑域。同样,不可变类型可以作为构建的基础类型。
特征
1. Private 和 Final 字段
包含对象状态的字段是 private 和 Final 。私有可见性可防止直接访问该字段,而最终可见性可确保该字段仅分配一次。
2.无修饰方法
私有字段不能在类外部访问。通常,分别提供访问方法(getter)和修饰符方法(setter)来读取和写入字段。为了确保一致性,不允许使用修饰符。
3.最后一堂课
允许类继承可能会破坏不变性。扩展不可变类的子类可以影响对象的状态。因此,该课程是最终的。
4. 防御性副本
在对象创建期间,不是将构造函数中的参数直接分配给私有字段,而是创建参数的深层副本(或不可变副本)将提供外部修改。
如果参数之一是引用类型,则可以在调用方端轻松对其进行操作。创建保护副本可以让您避免这种操作。
类似地,对于访问器(getters),不是直接引用内部字段,而是可以自由共享它的副本。
-
并非所有字段在构造函数中都有保护副本。这是因为 id 是原始类型,而 name 和 joinDate 字段是不可变类型。它们不能被调用者更改并且将保持不变,而成就字段需要使用 List.copyOf 方法创建的参数的副本。这是因为 copyOf 返回一个不可变的 List 。
-
同样,访问器方法直接返回字段而不是防御副本,因为所有字段类型都是不可变的(包括成就),因此不能在类外部进行修改。
理解 Java 的类路径与构建路径 |拜尔东 --- Understanding Java’s Classpath vs. Build Path | Baeldung
MANIFEST.MF 文件作用及格式要求
manifest.mf 文件格式如下
Manifest-Version: 1.0
Class-Path: xxx1.jar xxx2.jar xxx3.jar xxx4.jar
xxx5.jar
Main-Class: com.example.demo.DemoApplication
包括 Manifest 版本,Class-Path(类加载器通过这个路径找到要用的 jar 包)和 Main-Class(jar 文件的入口类)
注意:每项的冒号后有一个空格,Class-Path 中的 jar 文件要用空格分开,每行不超过 72 字符且开头要有一个空格,最后一行结尾不能有空格。文件最后一行要用回车结束。
jar 包之间用空格分开,行首有空格,所以注意有时行开头要有两个空格