Java 中文官方教程 2022 版(四十三)(1)

简介: Java 中文官方教程 2022 版(四十三)

课程:使扩展安全

原文:docs.oracle.com/javase/tutorial/ext/security/index.html

现在您已经了解如何使用扩展,您可能想知道扩展具有哪些安全权限。例如,如果您正在开发一个涉及文件 I/O 的扩展,您需要了解如何为读写文件授予适当的权限。相反,如果您考虑使用他人开发的扩展,您将希望清楚地了解扩展具有哪些安全权限,以及如何在需要时更改这些权限。

本课程向您展示了 Java™ 平台的安全架构如何处理扩展。您将了解到如何查看授予扩展软件的权限,并学会通过一些简单的步骤修改扩展权限。此外,您还将学习如何封装扩展中的包,以限制对代码指定部分的访问。

本课程分为两个部分:

为扩展设置权限

本节包含一些示例,展示了扩展必须满足哪些条件才能被授予权限执行安全敏感操作。

封装扩展中的包

您可以选择在扩展 JAR 文件中封装包作为额外的安全措施。如果一个包被封装,这意味着该包中定义的所有类必须来自一个单独的 JAR 文件。本节将向您展示如何修改扩展清单以封装扩展包。

附加文档

您将在本课程的适当位置找到与安全相关的链接和参考文档。

要获取有关安全的完整信息,您可以参考以下内容:

为扩展设置权限

原文:docs.oracle.com/javase/tutorial/ext/security/policy.html

如果有安全管理器生效,那么必须满足以下条件,以使任何软件,包括扩展软件,能够执行安全敏感操作:

  • 扩展中的安全敏感代码必须包装在PrivilegedAction对象中。
  • 安全管理器实施的安全策略必须授予扩展适当的权限。默认情况下,安装的扩展被授予所有安全权限,就好像它们是核心平台 API 的一部分。安全策略授予的权限仅适用于包装在PrivilegedAction实例中的代码。

让我们更详细地看一下这些条件,附带一些示例。

使用PrivilegedAction

假设你想要修改上一课程示例中扩展示例中的RectangleArea类,将矩形面积写入文件而不是标准输出。然而,向文件写入是一个涉及安全的操作,因此如果你的软件将在安全管理器下运行,你需要将你的代码标记为特权代码。你需要执行两个步骤来实现这一点:

  1. 你需要将执行安全敏感操作的代码放在类型为java.security.PrivilegedAction的对象的run方法中。
  2. 你必须将该PrivilegedAction对象作为参数传递给java.security.AccessControllerdoPrivileged方法。

如果我们将这些准则应用于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.jarjava.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_3Name头部相关联的,因此只有该包被封存。(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

原文:docs.oracle.com/javase/tutorial/reflect/index.html

反射的用途

反射通常被需要能够检查或修改在 Java 虚拟机中运行的应用程序的运行时行为的程序所使用。这是一个相对高级的特性,应该只由对语言基础知识有很好掌握的开发人员使用。在这种情况下,反射是一种强大的技术,可以使应用程序执行原本不可能的操作。

可扩展性特性

应用程序可以通过使用完全限定名称创建可扩展性对象的实例来利用外部、用户定义的类。

类浏览器和可视化开发环境

类浏览器需要能够枚举类的成员。可视化开发环境可以从反射中可用的类型信息中受益,帮助开发人员编写正确的代码。

调试器和测试工具

调试器需要能够检查类的私有成员。测试工具可以利用反射系统地调用类中定义的可发现的一组 API,以确保测试套件中的代码覆盖率较高。

反射的缺点

反射很强大,但不应该被滥用。如果可以在不使用反射的情况下执行操作,则最好避免使用它。在通过反射访问代码时应牢记以下问题。

性能开销

因为反射涉及动态解析的类型,某些 Java 虚拟机优化无法执行。因此,反射操作比非反射操作性能较慢,并且应该避免在性能敏感应用程序中频繁调用的代码段中使用。

安全限制

反射需要运行时权限,当在安全管理器下运行时可能不存在。这是一个重要的考虑因素,对于必须在受限制的安全上下文中运行的代码,比如在 Applet 中。

内部信息的暴露

由于反射允许代码执行在非反射代码中非法的操作,比如访问private字段和方法,使用反射可能导致意想不到的副作用,可能使代码失效并破坏可移植性。反射代码打破了抽象,因此可能会随着平台升级而改变行为。

路径课程

本路径涵盖了通过反射访问和操作类、字段、方法和构造函数的常见用法。每个课程包含代码示例、提示和故障排除信息。

本课程展示了获取Class对象的各种方法,并使用它来检查类的属性,包括其声明和内容。

本课程描述了如何使用 Reflection API 查找类的字段、方法和构造函数。提供了设置和获取字段值、调用方法以及使用特定构造函数创建对象实例的示例。

这节课介绍了两种特殊类型的类:在运行时生成的数组和定义唯一命名对象实例的enum类型。示例代码展示了如何检索数组的组件类型以及如何使用数组或enum类型设置和获取字段。


注意:

本教程中的示例旨在用于实验 Reflection API。因此,异常处理与在生产代码中使用的方式不同。特别是,在生产代码中,不建议将对用户可见的堆栈跟踪信息输出。


课程:类

原文:docs.oracle.com/javase/tutorial/reflect/class/index.html

每种类型都是引用类型或基本类型。类、枚举和数组(它们都继承自java.lang.Object)以及接口都是引用类型。引用类型的示例包括java.lang.String、所有基本类型的包装类,如java.lang.Double、接口java.io.Serializable和枚举javax.swing.SortOrder。基本类型有一组固定的类型:booleanbyteshortintlongcharfloatdouble

对于每种类型的对象,Java 虚拟机实例化一个不可变的java.lang.Class实例,该实例提供了用于检查对象的运行时属性的方法,包括其成员和类型信息。Class还提供了创建新类和对象的能力。最重要的是,它是所有反射 API 的入口点。本课程涵盖了涉及类的最常用的反射操作:

  • 检索类对象描述了获取Class的方法。
  • 检查类修饰符和类型展示了如何访问类声明信息。
  • 发现类成员说明了如何列出类中的构造函数、字段、方法和嵌套类。
  • 故障排除描述了在使用Class时遇到的常见错误。

检索类对象

原文: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();

返回StringClass

Class c = System.console().getClass();

与虚拟机关联的唯一控制台由static方法System.console()返回。getClass()返回的值是对应于java.io.ConsoleClass

enum E { A, B }
Class c = A.getClass();

A是枚举E的一个实例;因此getClass()返回对应于枚举类型EClass

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 的方法。每种原始类型和 voidjava.lang 中都有一个包装类,用于将原始类型装箱为引用类型。每个包装类都包含一个名为 TYPE 的字段,该字段等于被包装的原始类型的 Class

Class c = Double.TYPE;

存在一个类 java.lang.Double 用于包装原始类型 double,每当需要一个 Object 时。Double.TYPE 的值与 double.class 相同。

Class c = Void.TYPE;

Void.TYPEvoid.class 相同。

返回类的方法

有几个反射 API 返回类,但只有在已经直接或间接地获取了 Class 后才能访问这些类。

Class.getSuperclass()

返回给定类的超类。

Class c = javax.swing.JButton.class.getSuperclass();

javax.swing.JButton 的超类是 javax.swing.AbstractButton

Class.getClasses()

返回类的所有公共类、接口和枚举,包括继承的成员。

Class<?>[] c = Character.class.getClasses();

Character 包含两个成员类 Character.SubsetCharacter.UnicodeBlock

Class.getDeclaredClasses()

返回在此类中显式声明的所有类、接口和枚举。

Class<?>[] c = Character.class.getDeclaredClasses();

Character 包含两个公共成员类 Character.SubsetCharacter.UnicodeBlock 以及一个私有类 Character.CharacterCache

Class.getDeclaringClass()

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();

字段outSystem中声明。

public class MyClass {
    static Object o = new Object() {
        public void m() {} 
    };
    static Class<c> = o.getClass().getEnclosingClass();
}

o定义的匿名类的声明类为null

Class.getEnclosingClass()

返回类的直接封闭类。

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

一个类可以用一个或多个修饰符声明,这些修饰符会影响其运行时行为:

  • 访问修饰符:publicprotectedprivate
  • 要求覆盖的修饰符:abstract
  • 限制为一个实例的修饰符:static
  • 禁止值修改的修饰符:final
  • 强制严格浮点行为的修饰符:strictfp
  • 注解

并非所有修饰符都允许用于所有类,例如接口不能是final,枚举不能是abstractjava.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

相关文章
|
3月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
12天前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
26天前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
119 26
|
1月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
1月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
2月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
36 2
|
1月前
|
Java 数据库连接 编译器
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
Kotlin教程笔记(29) -Kotlin 兼容 Java 遇到的最大的“坑”
51 0
|
2月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
2月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
2月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编