课程:使扩展安全
现在您已经了解如何使用扩展,您可能想知道扩展具有哪些安全权限。例如,如果您正在开发一个涉及文件 I/O 的扩展,您需要了解如何为读写文件授予适当的权限。相反,如果您考虑使用他人开发的扩展,您将希望清楚地了解扩展具有哪些安全权限,以及如何在需要时更改这些权限。
本课程向您展示了 Java™ 平台的安全架构如何处理扩展。您将了解到如何查看授予扩展软件的权限,并学会通过一些简单的步骤修改扩展权限。此外,您还将学习如何封装扩展中的包,以限制对代码指定部分的访问。
本课程分为两个部分:
为扩展设置权限
本节包含一些示例,展示了扩展必须满足哪些条件才能被授予权限执行安全敏感操作。
封装扩展中的包
您可以选择在扩展 JAR 文件中封装包作为额外的安全措施。如果一个包被封装,这意味着该包中定义的所有类必须来自一个单独的 JAR 文件。本节将向您展示如何修改扩展清单以封装扩展包。
附加文档
您将在本课程的适当位置找到与安全相关的链接和参考文档。
要获取有关安全的完整信息,您可以参考以下内容:
为扩展设置权限
如果有安全管理器生效,那么必须满足以下条件,以使任何软件,包括扩展软件,能够执行安全敏感操作:
- 扩展中的安全敏感代码必须包装在
PrivilegedAction
对象中。 - 安全管理器实施的安全策略必须授予扩展适当的权限。默认情况下,安装的扩展被授予所有安全权限,就好像它们是核心平台 API 的一部分。安全策略授予的权限仅适用于包装在
PrivilegedAction
实例中的代码。
让我们更详细地看一下这些条件,附带一些示例。
使用PrivilegedAction
类
假设你想要修改上一课程示例中扩展示例中的RectangleArea
类,将矩形面积写入文件而不是标准输出。然而,向文件写入是一个涉及安全的操作,因此如果你的软件将在安全管理器下运行,你需要将你的代码标记为特权代码。你需要执行两个步骤来实现这一点:
- 你需要将执行安全敏感操作的代码放在类型为
java.security.PrivilegedAction
的对象的run
方法中。 - 你必须将该
PrivilegedAction
对象作为参数传递给java.security.AccessController
的doPrivileged
方法。
如果我们将这些准则应用于RectangleArea
类,我们的类定义将如下所示:
import java.io.*; import java.security.*; public final class RectangleArea { public static void writeArea(final java.awt.Rectangle r) { AccessController. doPrivileged(new PrivilegedAction() { public Object run() { try { int area = r.width * r.height; String userHome = System.getProperty("user.home"); FileWriter fw = new FileWriter( userHome + File.separator + "test" + File.separator + "area.txt"); fw.write("The rectangle's area is " + area); fw.flush(); fw.close(); } catch(IOException ioe) { System.err.println(ioe); } return null; } }); } }
这个类中的唯一方法writeArea
计算矩形的面积,并将面积写入名为area.txt
的文件中,该文件位于用户主目录下的test
目录中。
处理输出文件的安全敏感语句必须放在新的PrivilegedAction
实例的run
方法中。(注意,run
要求返回一个Object
实例。返回的对象可以是null
。)然后将新的PrivilegedAction
实例作为参数传递给AccessController.doPrivileged
的调用。
有关使用doPrivileged
的更多信息,请参阅JDK™文档中的特权块 API。
以这种方式将安全敏感代码包装在PrivilegedAction
对象中是使扩展能够执行安全敏感操作的第一个要求。第二个要求是:让安全管理器授予特权代码适当的权限。
使用安全策略指定权限
运行时生效的安全策略由策略文件指定。默认策略由 JRE 软件中的lib/security/java.policy
文件设置。
策略文件通过grant条目为软件分配安全权限。策略文件可以包含任意数量的 grant 条目。默认策略文件为安装的扩展程序具有以下 grant 条目:
grant codeBase "file:${{java.ext.dirs}}/*" { permission java.security.AllPermission; };
此条目指定由file:${{java.ext.dirs}}/*
指定的目录中的文件将被授予名为java.security.AllPermission
的权限。 (请注意,从 Java 6 开始,java.ext.dirs
指的是类似classpath
的目录路径,每个目录都可以容纳安装的扩展。)不难猜到java.security.AllPermission
授予安装的扩展程序可以授予的所有安全权限。
默认情况下,安装的扩展程序没有安全限制。只要安全敏感代码包含在作为doPrivileged
调用参数传递的PrivilegedAction
实例中,扩展软件就可以执行安全敏感操作,就好像没有安装安全管理器一样。
要限制授予扩展程序的权限,您需要修改策略文件。要拒绝所有扩展程序的所有权限,您可以简单地删除上述 grant 条目。
并非所有权限都像默认授予的java.security.AllPermission
那样全面。删除默认 grant 条目后,您可以为特定权限输入新的 grant 条目,包括:
java.awt.**AWTPermission**
java.io.**FilePermission**
java.net.**NetPermission**
java.util.**PropertyPermission**
java.lang.reflect.**ReflectPermission**
java.lang.**RuntimePermission**
java.security.**SecurityPermission**
java.io.**SerializablePermission**
java.net.**SocketPermission**
JDK 中的权限文档提供了关于每个权限的详细信息。让我们看看使用 RectangleArea 作为扩展所需的权限。
RectangleArea.writeArea
方法需要两个权限:一个用于确定用户主目录的路径,另一个用于写入文件。假设RectangleArea
类打包在文件area.jar
中,您可以通过向策略文件添加以下条目来授予写入权限:
grant codeBase "file:${java.home}/lib/ext/area.jar" { permission java.io.PropertyPermission "user.home", "read"; permission java.io.FilePermission "${user.home}${/}test${/}*", "write"; };
此条目的codeBase "file:${java.home}/lib/ext/area.jar"
部分保证此条目指定的权限仅适用于area.jar
。 java.io.PropertyPermission
允许访问属性。 第一个参数"user.home"
指定属性的名称,第二个参数"read"
表示可以读取该属性。(另一个选择是"write"
。)
java.io.FilePermission
允许访问文件。第一个参数"${user.home}${/}test${/}*"
表示area.jar
被授予访问用户主目录中test
目录中所有文件的权限。(请注意${/}
是一个平台无关的文件分隔符。)第二个参数表示被授予的文件访问权限仅限于写入。(第二个参数的其他选择是"read"
、"delete"
和"execute"
。)
签署扩展程序
你可以使用策略文件对扩展程序授予的权限施加额外限制,要求它们必须由受信任的实体签名。(有关签署和验证 JAR 文件的审查,请参阅本教程中的签署 JAR 文件课程。)
为了允许在授予权限时与扩展程序或其他软件一起进行签名验证,策略文件必须包含一个keystore 条目。密钥库条目指定用于验证的密钥库。密钥库条目的形式为
keystore "*keystore_url*";
URL keystore_url可以是绝对或相对的。如果是相对的,URL 是相对于策略文件的位置。例如,要使用keytool
使用的默认密钥库,将此条目添加到java.policy
中
keystore "file://${user.home}/.keystore";
要指示必须签署扩展程序才能被授予安全权限,您可以使用signedBy
字段。例如,以下条目指示只有当扩展程序area.jar
由密钥库中的别名为 Robert 和 Rita 的用户签名时,才授予列出的权限:
grant signedBy "Robert,Rita", codeBase "file:${java.home}/lib/ext/area.jar" { permission java.io.PropertyPermission "user.home", "read"; permission java.io.FilePermission "${user.home}${/}test${/}*", "write"; };
如果codeBase
字段被省略,如下所示的"grant",则权限将授予任何由"Robert"或"Rita"签名的软件,包括已安装或下载的扩展程序:
grant signedBy "Robert,Rita" { permission java.io.FilePermission "*", "write"; };
有关策略文件格式的更多详细信息,请参阅 JDK 文档中的安全体系结构规范第 3.3.1 节。
在扩展中封存包
原文:
docs.oracle.com/javase/tutorial/ext/security/sealing.html
您可以选择性地封存扩展 JAR 文件中的包作为额外的安全措施。如果一个包被封存,那么该包中定义的所有类必须来自一个单独的 JAR 文件。
没有封存,一个“敌对”的程序可以创建一个类并将其定义为您的扩展包的成员。然后,敌对软件将自由访问您的扩展包的包保护成员。
在扩展中封存包与封存任何 JAR 打包的类没有区别。要封存您的扩展包,您必须向包含您的扩展的 JAR 文件的清单中添加Sealed
头部。您可以通过将Sealed
头部与包的Name
头部关联来封存单个包。在存档中未与单个包关联的Sealed
头部表示所有包都被封存。这样的“全局”Sealed
头部将被与单个包关联的任何Sealed
头部覆盖。与Sealed
头部关联的值要么是true
,要么是false
。
例子
让我们看几个示例清单文件。在这些示例中,假设 JAR 文件包含这些包:
com/myCompany/package_1/ com/myCompany/package_2/ com/myCompany/package_3/ com/myCompany/package_4/
假设您想要封存所有包。您可以通过简单地向清单中添加一个存档级别的Sealed
头部来实现:
Manifest-Version: 1.0 Sealed: true
任何具有这个清单的 JAR 文件中的所有包都将被封存。
如果您只想封存com.myCompany.package_3
,您可以通过这个清单来实现:
Manifest-Version: 1.0 Name: com/myCompany/package_3/ Sealed: true
在这个例子中,唯一的Sealed
头部是与包com.myCompany.package_3
的Name
头部相关联的,因此只有该包被封存。(Sealed
头部与Name
头部相关联,因为它们之间没有空行。)
最后一个例子,假设您想要封存所有包除了com.myCompany.package_2
。您可以通过这样的清单来实现:
Manifest-Version: 1.0 Sealed: true Name: com/myCompany/package_2/ Sealed: false
在这个例子中,存档级别的Sealed: true
头部表示 JAR 文件中的所有包都将被封存。然而,清单中还有一个与包com.myCompany.package_2
相关联的Sealed: false
头部,该头部覆盖了该包的存档级别封存。因此,这个清单将导致所有包都被封存,除了com.myCompany.package_2
。
路径:反射 API
反射的用途
反射通常被需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序所使用。这是一个相对高级的特性,应该只由对语言基础知识有很好掌握的开发人员使用。在这种情况下,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。
可扩展性特性
应用程序可以通过使用完全限定名称创建可扩展性对象的实例来利用外部、用户定义的类。
类浏览器和可视化开发环境
类浏览器需要能够枚举类的成员。可视化开发环境可以从反射中可用的类型信息中受益,帮助开发人员编写正确的代码。
调试器和测试工具
调试器需要能够检查类的私有成员。测试工具可以利用反射系统地调用类中定义的可发现的一组 API,以确保测试套件中的代码覆盖率较高。
反射的缺点
反射很强大,但不应该被滥用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。在通过反射访问代码时应牢记以下问题。
性能开销
因为反射涉及动态解析的类型,某些 Java 虚拟机优化无法执行。因此,反射操作比非反射操作性能较慢,并且应该避免在性能敏感应用程序中频繁调用的代码段中使用。
安全限制
反射需要运行时权限,当在安全管理器下运行时可能不存在。这是一个重要的考虑因素,对于必须在受限制的安全上下文中运行的代码,比如在 Applet 中。
内部信息的暴露
由于反射允许代码执行在非反射代码中非法的操作,比如访问private
字段和方法,使用反射可能导致意想不到的副作用,可能使代码失效并破坏可移植性。反射代码打破了抽象,因此可能会随着平台升级而改变行为。
路径课程
本路径涵盖了通过反射访问和操作类、字段、方法和构造函数的常见用法。每个课程包含代码示例、提示和故障排除信息。
本课程展示了获取Class
对象的各种方法,并使用它来检查类的属性,包括其声明和内容。
本课程描述了如何使用 Reflection API 查找类的字段、方法和构造函数。提供了设置和获取字段值、调用方法以及使用特定构造函数创建对象实例的示例。
这节课介绍了两种特殊类型的类:在运行时生成的数组和定义唯一命名对象实例的enum
类型。示例代码展示了如何检索数组的组件类型以及如何使用数组或enum
类型设置和获取字段。
注意:
本教程中的示例旨在用于实验 Reflection API。因此,异常处理与在生产代码中使用的方式不同。特别是,在生产代码中,不建议将对用户可见的堆栈跟踪信息输出。
课程:类
每种类型都是引用类型或基本类型。类、枚举和数组(它们都继承自java.lang.Object
)以及接口都是引用类型。引用类型的示例包括java.lang.String
、所有基本类型的包装类,如java.lang.Double
、接口java.io.Serializable
和枚举javax.swing.SortOrder
。基本类型有一组固定的类型:boolean
、byte
、short
、int
、long
、char
、float
和double
。
对于每种类型的对象,Java 虚拟机实例化一个不可变的java.lang.Class
实例,该实例提供了用于检查对象的运行时属性的方法,包括其成员和类型信息。Class
还提供了创建新类和对象的能力。最重要的是,它是所有反射 API 的入口点。本课程涵盖了涉及类的最常用的反射操作:
检索类对象
原文:
docs.oracle.com/javase/tutorial/reflect/class/classNew.html
所有反射操作的入口点是java.lang.Class
。除了java.lang.reflect.ReflectPermission
之外,java.lang.reflect
中的类都没有公共构造函数。要访问这些类,需要在Class
上调用适当的方法。有几种方法可以获取Class
,具体取决于代码是否可以访问对象、类的名称、类型或现有的Class
。
Object.getClass()
如果对象的实例可用,则获取其Class
的最简单方法是调用Object.getClass()
。当然,这仅适用于所有继承自Object
的引用类型。以下是一些示例。
Class c = "foo".getClass();
Class c = System.console().getClass();
与虚拟机关联的唯一控制台由static
方法System.console()
返回。getClass()
返回的值是对应于java.io.Console
的Class
。
enum E { A, B } Class c = A.getClass();
A
是枚举E
的一个实例;因此getClass()
返回对应于枚举类型E
的Class
。
byte[] bytes = new byte[1024]; Class c = bytes.getClass();
由于数组是Objects
,因此也可以在数组实例上调用getClass()
。返回的Class
对应于具有组件类型byte
的数组。
import java.util.HashSet; import java.util.Set; Set<String> s = new HashSet<String>(); Class c = s.getClass();
在这种情况下,java.util.Set
是一个指向类型为java.util.HashSet
的对象的接口。getClass()
返回的值是与java.util.HashSet
对应的类。
.class 语法
如果类型可用但没有实例,则可以通过在类型名称后附加".class"
来获得一个Class
。这也是获取原始类型对应的Class
的最简单方式。
boolean b; Class c = b.getClass(); // compile-time error Class c = boolean.class; // correct
请注意,语句boolean.getClass()
会产生编译时错误,因为boolean
是原始类型,不能被解引用。.class
语法返回与类型boolean
对应的Class
。
Class c = java.io.PrintStream.class;
变量c
将是与类型java.io.PrintStream
对应的Class
。
Class c = int[][][].class;
.class
语法可用于检索与给定类型的多维数组对应的Class
。
Class.forName()
如果类的完全限定名称可用,则可以使用静态方法Class.forName()
获取相应的Class
。这不能用于原始类型。数组类名称的语法由Class.getName()
描述。此语法适用于引用和原始类型。
Class c = Class.forName("com.duke.MyLocaleServiceProvider");
此语句将根据给定的完全限定名称创建一个类。
Class cDoubleArray = Class.forName("[D"); Class cStringArray = Class.forName("[[Ljava.lang.String;");
变量cDoubleArray
将包含与原始类型double
的数组对应的Class
(即与double[].class
相同)。变量cStringArray
将包含与String
的二维数组对应的Class
(即与String[][].class
相同)。
原始类型包装器的 TYPE 字段
.class
语法是获取原始类型的 Class
更方便且更常用的方式;然而还有另一种获取 Class
的方法。每种原始类型和 void
在 java.lang
中都有一个包装类,用于将原始类型装箱为引用类型。每个包装类都包含一个名为 TYPE
的字段,该字段等于被包装的原始类型的 Class
。
Class c = Double.TYPE;
存在一个类 java.lang.Double
用于包装原始类型 double
,每当需要一个 Object
时。Double.TYPE
的值与 double.class
相同。
Class c = Void.TYPE;
Void.TYPE
与 void.class
相同。
返回类的方法
有几个反射 API 返回类,但只有在已经直接或间接地获取了 Class
后才能访问这些类。
返回给定类的超类。
Class c = javax.swing.JButton.class.getSuperclass();
javax.swing.JButton
的超类是 javax.swing.AbstractButton
。
返回类的所有公共类、接口和枚举,包括继承的成员。
Class<?>[] c = Character.class.getClasses();
Character
包含两个成员类 Character.Subset
和 Character.UnicodeBlock
。
返回在此类中显式声明的所有类、接口和枚举。
Class<?>[] c = Character.class.getDeclaredClasses();
Character
包含两个公共成员类 Character.Subset
和 Character.UnicodeBlock
以及一个私有类 Character.CharacterCache
。
java.lang.reflect.Field.getDeclaringClass()
。
java.lang.reflect.Method.getDeclaringClass()
。
java.lang.reflect.Constructor.getDeclaringClass()
。
返回声明这些成员的Class
。匿名类声明不会有声明类,但会有封闭类。
import java.lang.reflect.Field; Field f = System.class.getField("out"); Class c = f.getDeclaringClass();
public class MyClass { static Object o = new Object() { public void m() {} }; static Class<c> = o.getClass().getEnclosingClass(); }
由o
定义的匿名类的声明类为null
。
返回类的直接封闭类。
Class c = Thread.State.class().getEnclosingClass();
枚举Thread.State
的封闭类为Thread
。
public class MyClass { static Object o = new Object() { public void m() {} }; static Class<c> = o.getClass().getEnclosingClass(); }
由o
定义的匿名类由MyClass
封闭。
检查类的修饰符和类型
原文:
docs.oracle.com/javase/tutorial/reflect/class/classModifiers.html
一个类可以用一个或多个修饰符声明,这些修饰符会影响其运行时行为:
- 访问修饰符:
public
,protected
和private
- 要求覆盖的修饰符:
abstract
- 限制为一个实例的修饰符:
static
- 禁止值修改的修饰符:
final
- 强制严格浮点行为的修饰符:
strictfp
- 注解
并非所有修饰符都允许用于所有类,例如接口不能是final
,枚举不能是abstract
。java.lang.reflect.Modifier
包含了所有可能的修饰符声明。它还包含可用于解码由Class.getModifiers()
返回的修饰符集合的方法。
ClassDeclarationSpy
示例展示了如何获取类的声明组件,包括修饰符、泛型类型参数、实现的接口和继承路径。由于Class
实现了java.lang.reflect.AnnotatedElement
接口,因此也可以查询运行时注解。
import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import static java.lang.System.out; public class ClassDeclarationSpy { public static void main(String... args) { try { Class<?> c = Class.forName(args[0]); out.format("Class:%n %s%n%n", c.getCanonicalName()); out.format("Modifiers:%n %s%n%n", Modifier.toString(c.getModifiers())); out.format("Type Parameters:%n"); TypeVariable[] tv = c.getTypeParameters(); if (tv.length != 0) { out.format(" "); for (TypeVariable t : tv) out.format("%s ", t.getName()); out.format("%n%n"); } else { out.format(" -- No Type Parameters --%n%n"); } out.format("Implemented Interfaces:%n"); Type[] intfs = c.getGenericInterfaces(); if (intfs.length != 0) { for (Type intf : intfs) out.format(" %s%n", intf.toString()); out.format("%n"); } else { out.format(" -- No Implemented Interfaces --%n%n"); } out.format("Inheritance Path:%n"); List<Class> l = new ArrayList<Class>(); printAncestor(c, l); if (l.size() != 0) { for (Class<?> cl : l) out.format(" %s%n", cl.getCanonicalName()); out.format("%n"); } else { out.format(" -- No Super Classes --%n%n"); } out.format("Annotations:%n"); Annotation[] ann = c.getAnnotations(); if (ann.length != 0) { for (Annotation a : ann) out.format(" %s%n", a.toString()); out.format("%n"); } else { out.format(" -- No Annotations --%n%n"); } // production code should handle this exception more gracefully } catch (ClassNotFoundException x) { x.printStackTrace(); } } private static void printAncestor(Class<?> c, List<Class> l) { Class<?> ancestor = c.getSuperclass(); if (ancestor != null) { l.add(ancestor); printAncestor(ancestor, l); } } }
Java 中文官方教程 2022 版(四十三)(2)https://developer.aliyun.com/article/1488264