1. 概述

本文将深入探讨 Java 内置的安全机制——SecurityManager,它在默认情况下是禁用的。我们将重点分析其核心组件、扩展点以及配置方式,帮助你在需要时快速上手。

虽然这个机制如今已逐渐退出历史舞台,但在某些特定场景下仍有参考价值。理解它,不仅有助于排查老系统中的安全限制问题,也能加深你对 Java 安全模型演进的理解。

2. SecurityManager 实际运行效果

你可能没想到,一旦启用默认配置的 SecurityManager很多我们习以为常的操作都会被直接拦截

来看一个简单例子:

System.setSecurityManager(new SecurityManager());
new URL("http://www.google.com").openConnection().connect();

上面这段代码做了两件事:

  1. 通过 System.setSecurityManager() 启用默认安全管理器
  2. 尝试访问 Google

结果?直接抛出异常:

java.security.AccessControlException: access denied ("java.net.SocketPermission"
  "www.google.com:80" "connect,resolve")

踩坑提示:这说明,哪怕只是发起一次 HTTP 请求,底层也会触发 SocketPermission 权限检查。

类似的受限操作还有很多,比如:

  • ❌ 读取系统属性(System.getProperty
  • ❌ 读取环境变量(System.getenv
  • ❌ 文件读写
  • ❌ 反射调用敏感方法
  • ❌ 修改本地化设置(Locale)

这些操作在标准库中都会被自动包装成 Permission 请求,交由 SecurityManager 决策是否放行。

3. 使用场景与现状

SecurityManager 自 Java 1.0 就已存在,它的黄金时代是 Applet(小程序)盛行的年代。浏览器中运行的 Java 小程序必须受到严格限制,不能随意访问文件系统或网络,否则就是重大安全漏洞。

但如今:

  • ❌ Applet 已被淘汰(各大浏览器早已不再支持)
  • ⚠️ 第三方代码沙箱需求减少
  • ✅ 仅少数插件化系统或老旧中间件仍在使用

更重要的是,从 Java 17 开始,SecurityManager 已被正式标记为废弃,并计划在未来版本中移除

主要原因包括:

  • ✅ 配置复杂,策略文件语法晦涩
  • ✅ 开发调试困难,权限异常难以定位
  • ✅ 与现代模块系统(JPMS)不兼容
  • ✅ 实际安全防护效果有限,容易绕过

现代应用更倾向于使用以下替代方案:

  • 代码签名(Code Signing):验证代码来源可信
  • 容器化(Containerization):通过 Docker 等隔离进程资源
  • 操作系统级安全机制:如 Linux 的 Capability、Seccomp、命名空间等

因此,SecurityManager 被认为是一种过时的安全模型,不再推荐用于新项目。

4. 设计原理

4.1. SecurityManager

核心类是 java.lang.SecurityManager,它提供了一系列以 checkXxx 命名的方法,例如:

  • checkConnect()
  • checkRead()
  • checkPropertyAccess()
  • checkPermission(Permission perm)

所有其他 checkXxx 方法最终都会委托给 checkPermission() 进行统一决策。

当你调用 System.getSecurityManager().checkPermission(perm) 时,如果当前上下文没有对应权限,就会抛出 AccessControlException

4.2. Permission

java.security.Permission 是权限的抽象表示。JDK 中几乎所有敏感操作都会创建对应的 Permission 实例,例如:

  • FilePermission:文件读写
  • SocketPermission:网络连接
  • PropertyPermission:系统属性访问
  • RuntimePermission:如 exitVMgetClassLoader

你可以自定义权限类,继承 BasicPermissionPermission 即可。

4.3. 配置方式

权限通过策略文件(policy file)配置,格式如下:

grant codeBase "file:${java.ext.dirs}/*" {
    permission java.security.AllPermission;
};

关键点:

  • codeBase:指定代码来源路径(可选)
  • signedBy:指定必须由某个证书签名的代码才授予权限
  • principal:基于当前 Subject 的主体信息做授权判断
  • ✅ 支持任意组合条件

默认加载顺序:

  1. 系统策略文件:$JAVA_HOME/lib/security/java.policy
  2. 用户策略文件:~/.java.policy(自动追加到系统策略后)

也可以通过 JVM 参数指定:

# 追加策略
-Djava.security.policy=/my/policy-file

# 替换所有默认策略(双等号)
-Djava.security.policy==/my/policy-file

⚠️ 注意:路径中的变量如 ${java.ext.dirs} 会被自动解析。

5. 实战示例

我们来动手实现一个自定义权限控制。

步骤一:定义自定义权限

public class CustomPermission extends BasicPermission {
    public CustomPermission(String name) {
        super(name);
    }

    public CustomPermission(String name, String actions) {
        super(name, actions);
    }
}

步骤二:创建受保护的服务

public class Service {

    public static final String OPERATION = "my-operation";

    public void operation() {
        SecurityManager securityManager = System.getSecurityManager();
        if (securityManager != null) {
            securityManager.checkPermission(new CustomPermission(OPERATION));
        }
        System.out.println("Operation is executed");
    }
}

步骤三:测试无权限情况

启用安全管理器后运行:

System.setSecurityManager(new SecurityManager());
new Service().operation();

结果抛出异常:

java.security.AccessControlException: access denied
  ("com.baeldung.security.manager.CustomPermission" "my-operation")

    at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472)
    at java.security.AccessController.checkPermission(AccessController.java:884)
    at java.lang.SecurityManager.checkPermission(SecurityManager.java:549)
    at com.baeldung.security.manager.Service.operation(Service.java:10)

步骤四:添加授权策略

在用户主目录下创建 ~/.java.policy 文件,内容如下:

grant codeBase "file:/path/to/your/classes/" {
    permission com.baeldung.security.manager.CustomPermission "my-operation";
};

📌 注意替换 /path/to/your/classes/ 为实际的 class 文件路径。

再次运行程序,输出:

Operation is executed

✅ 成功!说明权限已正确授予。

6. 总结

尽管 SecurityManager 已被废弃,但它仍是 Java 安全体系演化过程中的重要一环。通过本文你应已掌握:

  • ✅ 它如何拦截高风险操作
  • ✅ 权限模型的核心组件:SecurityManagerPermission、policy 文件
  • ✅ 自定义权限的实现方式
  • ✅ 为何它被现代应用弃用

📌 建议:新项目无需再使用 SecurityManager,优先考虑容器隔离、代码签名或应用层鉴权等更现代的方案。

文中所有示例代码均可在 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-security


原始标题:Intro to the Java SecurityManager | Baeldung