1. 概述
本文将深入探讨 Java 内置的安全机制——SecurityManager
,它在默认情况下是禁用的。我们将重点分析其核心组件、扩展点以及配置方式,帮助你在需要时快速上手。
虽然这个机制如今已逐渐退出历史舞台,但在某些特定场景下仍有参考价值。理解它,不仅有助于排查老系统中的安全限制问题,也能加深你对 Java 安全模型演进的理解。
2. SecurityManager 实际运行效果
你可能没想到,一旦启用默认配置的 SecurityManager
,很多我们习以为常的操作都会被直接拦截。
来看一个简单例子:
System.setSecurityManager(new SecurityManager());
new URL("http://www.google.com").openConnection().connect();
上面这段代码做了两件事:
- 通过
System.setSecurityManager()
启用默认安全管理器 - 尝试访问 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
:如exitVM
、getClassLoader
等
你可以自定义权限类,继承 BasicPermission
或 Permission
即可。
4.3. 配置方式
权限通过策略文件(policy file)配置,格式如下:
grant codeBase "file:${java.ext.dirs}/*" {
permission java.security.AllPermission;
};
关键点:
codeBase
:指定代码来源路径(可选)signedBy
:指定必须由某个证书签名的代码才授予权限principal
:基于当前 Subject 的主体信息做授权判断- ✅ 支持任意组合条件
默认加载顺序:
- 系统策略文件:
$JAVA_HOME/lib/security/java.policy
- 用户策略文件:
~/.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 安全体系演化过程中的重要一环。通过本文你应已掌握:
- ✅ 它如何拦截高风险操作
- ✅ 权限模型的核心组件:
SecurityManager
、Permission
、policy 文件 - ✅ 自定义权限的实现方式
- ✅ 为何它被现代应用弃用
📌 建议:新项目无需再使用 SecurityManager
,优先考虑容器隔离、代码签名或应用层鉴权等更现代的方案。
文中所有示例代码均可在 GitHub 获取:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-security