跳到主要内容

JEP 411:弃用安全管理器以进行删除

概括

弃用安全管理器以在未来版本中删除。安全管理器可以追溯到 Java 1.0。多年来,它一直不是保护客户端 Java 代码的主要手段,也很少用于保护服务器端代码。为了推动 Java 向前发展,我们打算弃用安全管理器,以便与旧版 Applet API ( JEP 398 ) 一起删除。

目标

  • 让开发人员做好在未来版本的 Java 中删除安全管理器的准备。

  • 如果用户的 Java 应用程序依赖安全管理器,则警告用户。

  • 评估是否需要新的 API 或机制来解决使用安全管理器的特定狭窄用例,例如阻塞System::exit

非目标

提供安全管理器的替代品并不是我们的目标。未来的 JEP 或增强功能可能会根据需求为特定用例定义新的 API 或机制。

动机

Java平台强调安全性。数据_的完整性_受到Java 语言和 VM 内置内存安全性的保护:变量在使用前初始化,检查数组边界,内存释放完全自动。同时,数据的_机密性_受到 Java 类库对现代加密算法和协议(例如 SHA-3、EdDSA 和 TLS 1.3)的可信实现的保护。安全性是一门动态的科学,因此我们不断更新 Java 平台以解决新的漏洞并反映新的行业态势,例如通过弃用弱加密协议。

安全管理器是一个长期存在的安全元素,它可以追溯到 Java 1.0。在通过 Web 浏览器下载 Java 小程序的时代,安全管理器通过在_沙箱_中运行小程序来保护用户计算机的完整性和数据的机密性,沙箱拒绝访问文件系统或网络等资源。 Java 类库的规模较小(java.*Java 1.0 中只有 8 个包),使得代码可以java.io在执行任何操作之前咨询安全管理器。安全管理器在_不可信代码_(来自远程计算机的小程序)和_可信代码_(本地计算机上的类)之间划出了一条清晰的界限:它将批准所有涉及可信代码资源访问的操作,但拒绝不可信代码的所有操作。

随着人们对 Java 兴趣的增长,我们引入了_签名小程序_,以允许安全管理器信任远程代码,从而允许小程序访问与通过java命令行运行的本地代码相同的资源。同时,Java 类库正在迅速扩展——Java 1.1 引入了 JavaBeans、JDBC、Reflection、RMI 和序列化——这意味着可信代码可以访问重要的新资源,例如数据库连接、RMI 服务器和反射对象。允许所有受信任的代码访问所有资源是不可取的,因此在 Java 1.2 中,我们重新设计了安全管理器,专注于应用_最小权限原则:_默认情况下,所有代码都将被视为不受信任,并受到沙盒式控制的约束,阻止访问资源,用户将通过授予特定的代码库访问特定资源的特定权限来信任特定的代码库。理论上,类路径上的应用程序 JAR 在如何使用 JDK 方面可能比来自 Internet 的小程序受到更多限制。限制权限被视为限制代码体中可能存在的任何漏洞影响的一种方法——实际上是一种深度防御机制。

因此,安全经理的目标是防范两种威胁:恶意意图(特别是在远程代码中)和_意外漏洞_(特别是在本地代码中)。

由于 Java 平台不再支持 applet,远程代码的恶意威胁已经消退。 Applet API已于 2017 年在 Java 9 中弃用,然后在 2021 年的 Java 17 中弃用并删除,并打算在未来版本中将其删除。运行小程序的闭源浏览器插件与闭源 Java Web Start 技术一起于 2018 年从 Oracle JDK 11 中删除。因此,安全管理器防范的许多风险不再重要。此外,安全管理器无法防范许多现在很严重的风险。安全管理器无法解决2020 年行业领导者确定的 25 个最危险问题中的 19 个,因此 XML 外部实体引用 (XXE) 注入和不正确的输入验证等问题需要在 Java 类库中采取直接对策。 (例如,JAXP 可以防止 XXE 攻击和 XML 实体扩展,而序列化过滤可以防止恶意数据在造成任何损害之前被反序列化。)安全管理器也无法防止基于推测执行漏洞的恶意行为

不幸的是,安全管理器缺乏针对恶意意图的功效,因为安全管理器必须融入到 Java 类库的结构中。因此,这是一个持续的维护负担。必须评估所有新功能和 API,以确保它们在启用安全管理器时正常运行。基于最小权限原则的访问控制在Java 1.0的类库中可能已经可行,但是java.*javax.*包的快速增长导致整个JDK出现了数十个权限和数百个权限检查。这是一个需要保持安全的重要领域,特别是因为权限可以以令人惊讶的方式交互。例如,某些权限允许应用程序或库代码执行一系列安全操作,其总体效果非常不安全,如果直接授予,则需要更强大的权限。

本地代码中意外漏洞的威胁几乎不可能用安全管理器来解决。许多声称安全管理器被广泛用于保护本地代码的说法经不起推敲;它在生产中的使用量比许多人想象的要少得多。其不被使用的原因有很多:

  • 脆弱的权限模型- 希望从安全管理器中受益的应用程序开发人员必须仔细授予应用程序执行的所有操作所需的所有权限。没有办法实现部分安全,即只有少数资源受到访问控制。例如,假设开发人员担心对数据的非法访问,因此希望授予仅从特定目录读取文件的权限。授予文件读取权限是不够的,因为除了读取文件(例如写入文件)之外,应用程序几乎肯定会使用 Java 类库中的其他操作,而这些其他操作将被安全管理器拒绝,因为代码不会具有适当的许可。只有仔细记录其代码如何与 Java 类库中的安全敏感操作交互的开发人员才能授予必要的权限。这不是常见的开发人员工作流程。 (安全管理器不允许表示“授予除读取文件之外的所有操作的权限”的负权限。)

  • 困难的编程模型- 安全管理器通过检查导致操作的所有运行代码的权限来批准安全敏感操作。这使得编写与安全管理器一起运行的库变得困难,因为库开发人员不足以记录其库代码所需的权限。除了已经授予该代码的任何权限之外,使用该库的应用程序开发人员还必须向其应用程序代码授予相同的权限。这违反了最小权限原则,因为应用程序代码可能不需要库的权限来执行其自己的操作。库开发人员可以通过仔细使用 APIjava.security.AccessController请求安全管理器仅考虑库的权限来缓解权限的病毒式增长,但此和其他安全编码准则的复杂性远远超出了大多数开发人员的兴趣。对于应用程序开发人员来说,阻力最小的途径通常是授予AllPermission任何相关的 JAR 文件,但这又违背了最小权限原则。

  • 性能差——安全管理器的核心是复杂的访问控制算法,它通常会带来不可接受的性能损失。因此,对于在命令行上运行的 JVM,安全管理器始终默认处于禁用状态。这进一步降低了开发人员投资使库和应用程序与安全管理器一起运行的兴趣。缺乏帮助推断和验证权限的工具是另一个障碍。

自安全管理器推出以来的四分之一个世纪里,采用率一直很低。只有少数应用程序附带限制其自身操作的策略文件(例如,ElasticSearch)。同样,只有少数框架附带策略文件(例如Tomcat),使用这些框架构建应用程序的开发人员仍然面临着确定自己的代码和他们使用的库所需的权限这一几乎难以克服的挑战。一些框架(例如NetBeans)避开策略文件,而是实现自定义安全管理器,以防止插件调用System::exit或深入了解代码的行为,例如它是否打开文件和网络连接 - 我们认为更适合的用例通过其他方式。

总之,人们对使用安全管理器开发现代 Java 应用程序没有太大兴趣。根据权限做出访问控制决策既笨重又缓慢,而且在整个行业中已经失宠;例如,.NET不再支持它。通过在 Java 平台的较低级别提供完整性,可以更好地实现安全性 — 例如,通过加强模块边界 ( JEP 403 ) 以防止访问 JDK 实现细节,并强化实现本身— 以及通过将整个 Java 运行时与敏感信息隔离通过进程外机制(例如容器和虚拟机管理程序)获取资源。为了推动 Java 平台向前发展,我们将弃用旧的安全管理器技术,并将其从 JDK 中删除。我们计划在多个版本中弃用和削弱安全管理器的功能,同时为阻塞等任务System::exit和其他被认为重要到足以进行替代的用例创建替代 API。

描述

在 Java 17 中,我们将:

  • 弃用并删除大多数与安全管理器相关的类和方法。

  • 如果在命令行上启用了安全管理器,则在启动时发出警告消息。

  • 如果 Java 应用程序或库动态安装安全管理器,则在运行时发出警告消息。

在 Java 18 中,我们将阻止 Java 应用程序或库动态安装安全管理器,除非最终用户明确选择允许。从历史上看,Java 应用程序或库总是被允许动态安装安全管理器,但自 Java 12 以来,最终用户已经能够通过在命令行上将系统属性设置java.security.manager为( ) 来阻止它——这会导致抛出。从 Java 18 开始,如果没有另外设置,则默认值将通过.因此,调用的应用程序和库可能会因意外的.为了像以前一样工作,最终用户必须在命令行上设置为 ( ) 。disallow``java -Djava.security.manager=disallow ...``System::setSecurityManager``UnsupportedOperationException``java.security.manager``disallow``java -D...``System::setSecurityManager``UnsupportedOperationException``System::setSecurityManager``java.security.manager``allow``java -Djava.security.manager=allow ...

在 Java 18 之后的功能版本中,我们将降级其他安全管理器 API,以便它们保留在原处,但功能有限或没有功能。例如,我们可以AccessController::doPrivileged简单地修改为运行给定的操作,或者修改System::getSecurityManager为始终返回null。这将允许支持安全管理器并针对以前的 Java 版本编译的库继续工作,而无需更改甚至重新编译。一旦兼容性风险降至可接受的水平,我们预计将删除这些 API。

在 Java 18 之后的功能版本中,我们可能会更改 Java SE API 定义,以便以前执行权限检查的操作不再执行它们,或者在启用安全管理器时执行更少的检查。因此,@throws SecurityExceptionAPI 规范中会出现的方法较少。

弃用要删除的 API

安全管理器由和包中的类java.lang.SecurityManager和许多密切相关的 API组成。我们将最终弃用以下八个类和两个方法,并用 注释它们:java.lang``java.security``@Deprecated(forRemoval=true)

  • java.lang.SecurityManager— 安全管理器的主要 API。

  • java.lang.System::{setSecurityManager, getSecurityManager}— 设置和获取安全管理器的方法。

  • java.security.{Policy, PolicySpi, Policy.Parameters}— 策略的主要 API,用于确定安全管理器下运行的代码是否已被授予执行特定特权操作的权限。

  • java.security.{AccessController, AccessControlContext, AccessControlException, DomainCombiner}— 访问控制器的主要 API,这是安全管理器委派权限检查的默认实现。如果没有安全管理器,这些 API 就没有价值,因为如果没有虚拟机中的策略实现和访问控制上下文支持,某些操作将无法工作。

我们还将最终弃用以下两个强烈依赖于安全管理器的类和八个方法:

  • java.lang.Thread::checkAccessjava.lang.ThreadGroup::checkAccessjava.util.logging.LogManager::checkAccess— 这三种方法是异常的,因为它们允许普通 Java 代码检查是否可以信任执行某些操作,而无需实际执行这些操作。如果没有安全管理器,它们就没有任何作用。

  • java.util.concurrent.Executors::{privilegedCallable, privilegedCallableUsingCurrentClassLoader, privilegedThreadFactory}— 这些实用方法仅在启用安全管理器时才有用。

  • java.rmi.RMISecurityManager— RMI 的安全管理器类。该类已过时并在 Java 8 中已弃用。

  • javax.security.auth.SubjectDomainCombiner-**javax.security.auth.Subject::{doAsPrivileged, getSubject}**用于基于用户的授权的 API,这些 API 依赖于安全管理器 API,例如AccessControlContextDomainCombiner。我们计划提供一个替代 API,因为Subject::getSubject它通常用于不需要安全管理器的用例,并继续支持涉及的用例Subject::doAs(见下文)。

由于各种原因,我们不会弃用java.security包中与安全管理器相关的某些类:

  • SecureClassLoader— 的超类java.net.URLClassLoader。此外,从 Java 9 开始,它对于应用程序类加载器和平台类加载器SecureClassLoader的实现也很有帮助。

  • CodeSource— 尽管最常与基于代码位置授予权限相关,但CodeSource并不直接与安全管理器相关,并且可以提供独立的价值作为识别代码主体的来源以及(可选)谁签名的手段。

  • ProtectionDomain— 几个重要的 API 依赖于ProtectionDomain,例如ClassLoader::defineClassClass::getProtectionDomainProtectionDomain还具有独立于安全管理器的值,因为它包含CodeSource类的 。

  • **Permission**和子类 - 其他重要的类,例如ProtectionDomain,依赖于Permission。然而,许多子类Permission都是特定于用例的,在安全管理器被删除后,这些用例可能不再相关。这些子类的维护者可以在评估兼容性风险后分别弃用和删除它们。

  • PermissionCollectionPermissions— 保存对象集合Permission且不直接依赖于安全管理器的类。

  • PrivilegedActionPrivilegedExceptionActionPrivilegedActionException— 这些 API 不直接依赖于安全管理器,并且由javax.security.authAPI 用于身份验证和授权(见下文)。

  • SecurityException— 权限检查失败时 Java API 抛出的运行时异常。我们可能会在以后弃用此 API 并将其删除,但目前这样做的影响太大了。

我们不会弃用该javax.security.auth.Subject::doAs方法,因为它可以Subject通过将 a 附加到线程的 来跨 API 边界传输AccessControlContext,其用途类似于 a ThreadLocalSubject然后可以通过调用 来通过底层身份验证机制(例如,GSSAPI 的 Kerberos 实现)获取 的凭据Subject::getSubject。这些凭据可用于身份验证或授权目的,并且不需要启用安全管理器。但是,Subject::doAs依赖于与安全管理器紧密相关的 API,例如AccessControlContextDomainCombiner。因此,我们计划创建一个不依赖于安全管理器 API 的新 API;随后我们将弃用该Subject::doAsAPI 以进行删除。

我们不会弃用任何工具。 (我们删除了policytoolJDK 10 中用于编辑策略文件的 GUI。)

发出警告

我们将进行以下更改,以确保开发人员和用户了解安全管理器已弃用并删除。

  • 如果在启动时启用默认安全管理器或自定义安全管理器:

    java -Djava.security.manager                    MyApp
    java -Djava.security.manager="" MyApp
    java -Djava.security.manager=default MyApp
    java -Djava.security.manager=com.foo.bar.Server MyApp

    然后启动时会发出以下警告:

    WARNING: A command line option has enabled the Security Manager
    WARNING: The Security Manager is deprecated and will be removed in a future release

    与编译时弃用警告不同,此警告无法被抑制。

    (上面显示的四个调用分别java -D...将系统属性设置java.security.manager为空字符串、空字符串、字符串default和自定义安全管理器的类名。这些调用是在启动时启用安全管理器的受支持方法Java 版本先于 Java 12。Java 12添加了对字符串和 的支持,如下所示。)allow``disallow

  • 如果安全管理器在启动时未启用,但可以在运行时动态安装:

    java MyApp
    java -Djava.security.manager=allow MyApp

    那么启动时不会发出警告。相反,调用时会在运行时发出警告System::setSecurityManager,如下所示:

    WARNING: A terminally deprecated method in java.lang.System has been called
    WARNING: System::setSecurityManager has been called by com.foo.bar.Server (file:/tmp/foobarserver/thing.jar)
    WARNING: Please consider reporting this to the maintainers of com.foo.bar.Server
    WARNING: System::setSecurityManager will be removed in a future release

    此警告对每个调用者显示一次,并且与编译时弃用警告不同,无法抑制。

  • 如果启动时未启用安全管理器,并且系统属性java.security.manager设置为disallow

    java -Djava.security.manager=disallow MyApp

    那么,如果尝试通过调用动态安装安全管理器,则启动时不会发出警告,运行时也不会发出警告System::setSecurityManager。但是,每次调用都会System::setSecurityManager抛出一个UnsupportedOperationException带有以下详细消息的消息:

    The Security Manager is deprecated and will be removed in a future release

    在 Java 18 中,disallow将成为java.security.manager.命令行将具有与Java 17 上java MyApp相同的效果。java -Djava.security.manager=disallow MyApp

未来的工作

此 JEP 旨在弃用安全管理器以便将来删除;现在不建议删除安全管理器。因此,有时间考虑安全管理器目前有用的用例,以及开发其某些功能的替代品或替代方案可能是合理的。以下是潜在增强功能和当前正在进行的工作的列表:

  • 保护对本机代码的访问- 使用安全管理器运行的应用程序可以使用权限来阻止加载本机代码,以便无法通过 Java 本机接口 (JNI) 访问它。计划取代 JNI 的是外部函数和内存 API (JEP 412),它提供了一个 Java API,用于与 Java 运行时之外的代码和数据进行互操作;它将保护对本机代码的访问,而无需依赖安全管理器。

  • 监视对资源的访问- 与安全管理器一起运行的应用程序有时会使用它来监视或记录文件和网络访问等操作,但不一定限制这些操作。有可能更好的方法来监视这些类型的活动,例如使用JDK Flight Recorder。我们将评估为网络、文件系统和流程创建添加新的 JFR 事件,目的是提高应用程序安全性并提供对执行此类操作的平台 API 的深入了解。

  • 阻止System::exit— 某些 IDE 和框架使用自定义安全管理器来阻止应用程序调用此方法。该用例可能会受益于新的 API

  • 保护反序列化- 如果未正确授予权限,则使用安全管理器运行并反序列化数据的应用程序很容易受到攻击(例如,请参阅Java 安全编码指南 8-5)。或者,序列化过滤器 (JEP 290)允许尽早验证传入数据,而上下文特定的反序列化过滤器 (JEP 415)将提高验证的灵活性和粒度。

  • 确保 XML 处理的安全——正如动机中所描述的,JAXP 有一种更安全地处理 XML 的模式。此模式是可选的,但当应用程序与安全管理器一起运行时,它也会默认启用。我们将研究默认启用此模式,无论是否启用安全管理器。 (XML 签名具有类似的安全验证模式,该模式是可选的,但在启用安全管理器时默认启用。从 Java 17 开始,无论是否启用安全管理器,该模式默认启用。)

备择方案

  • 保留安全管理器 API,以便希望拦截、记录和否决对资源的访问的自定义安全管理器进行扩展- 删除对策略文件的支持,但保留嵌入到 Java 类库中的权限检查机制。

    此选项迫使开发人员学习安全管理器架构的原则和最佳实践,包括复杂的权限检查科学,以实现更简单的资源监控目标。它还可能会引发这样的问题:是否值得在 JDK 中保留所有权限检查、安全管理器调用挂钩 - at System::exit,是的,at System::getProperty,也许不值得。我们认为我们应该研究提供类似功能的改进选项,例如JDK Flight Recorder

  • 增强安全管理器- 增强安全管理器来解决新的用例或解决其许多缺点是不切实际的。默认情况下启用安全管理器、使用 运行代码AllPermission并记录所有权限检查来鼓励开发人员更认真地对待它是不明智的。

  • 将安全管理器保留在适当的位置- 继续按原样支持它,而不进一步投资来改进它。

这些替代方案中的每一个都需要将安全管理器保留为接近其当前形式。经过几十年的维护安全管理器但看到很少的使用,我们不再愿意承担这种持续且昂贵的负担。

测试

我们将添加新的测试来验证在命令行上启用安全管理器或在运行时动态安装安全管理器时是否会发出警告。

风险和假设

  • 自 JDK 1.0 起,安全管理器就成为 Java 平台的一部分,因此某些应用程序可能会受到其弃用和最终删除的影响。不过,安全管理器的全部功能将在此 JEP 的目标版本中保留。当应用程序迁移到更新的 API 和机制时,它们可以在一段时间内继续依赖受支持的 JDK。

  • Jakarta EE对安全管理器有多项要求。我们假设这些要求将放宽或删除,以便在安全管理器降级并删除后,合规应用程序可以在未来的 Java 版本上运行。