Java 里面的 PrivilegedAction 和 PrivilegedExceptionAction
读 JDK 里面的代码, 可能会遇到某些操作需要被封装成 PrivilegedAction 和 PrivilegedExceptionAction 来执行, 比如下面的代码块:
AccessControlContext acc = (System.getSecurityManager() != null)
? AccessController.getContext()
: null;
PrivilegedAction<Boolean> action = new PrivilegedAction<>() {
public Boolean run() { return findSomething(); }
};
AccessController.doPrivileged(action, acc);
为什么需要特权操作(PrivilegedAction)?
Java 诞生的初期在浏览器的环境执行(applet), 所以要加很多安全限制, 从 1.0 版本就有了 SecurityManager 的概念, 从最核心的 System 类的 System.getSecurityManager()
你就能得到系统的安全管理器.
只不过, 这个安全管理器默认是没有开启的. 并且从 JDK 17 开始将要被废弃.
安全管理器是如何工作的?
安全管理器(SecurityManager)是通过policy来限制你能不能做某个操作. 比如: 代码能不能访问网络, 能不能读取磁盘文件, 能不能访问环境变量等. Policy 存放在一些文件里面, 通过改变文件里面policy的内容, 设置安全管理器是允许还是拒绝某些从左.
一个 policy 的例子如下: 下面的 policy 表示在运行时对于代码模块 java.scripting
中的代码, 授予所有的权限.
grant codeBase "jrt:/java.scripting" {
permission java.security.AllPermission;
};
你能在 <java.home>/lib/security/default.policy
里面找到系统默认的 policy. 当然你也可以定义自己的policy, 放到 ${java.home}/conf/security/java.policy
里面. 或者放到其它地方, 并且在启动参数里面通过: –Djava.security.policy=/tmp/myPolicy.policy
指定.
PrivilegedAction 和 PrivilegedExceptionAction 是如何工作的?
PrivilegedAction 和 PrivilegedExceptionAction 都会封装一个操作, 当这个操作会抛出 checked exception 的时候, 就需要用 PrivilegedExceptionAction
, 否则就用 PrivilegedAction
.
封装完这操作, 就使用 AccessController.doPrivileged(action, acc)
去执行, 它会检查是不是有执行权限, 如果有, 就去执行, 否则不执行.
代码示例
下面的代码先设置一个安全管理器, 然后尝试访问一个URL, 这个时候, 就会报错.
import java.io.IOException;
import java.net.URL;
public class Main {
public static void main(String[] args) {
System.setSecurityManager(new SecurityManager());
try {
new URL("https://www.tianxiaohui.com").openConnection().connect();
} catch (IOException e) {
e.printStackTrace();
}
}
}
错误: 拒绝访问:
Exception in thread "main" java.security.AccessControlException: access denied ("java.net.SocketPermission" "www.tianxiaohui.com:443" "connect,resolve")
at java.base/java.security.AccessControlContext.checkPermission(AccessControlContext.java:485)
at java.base/java.security.AccessController.checkPermission(AccessController.java:1068)
at java.base/java.lang.SecurityManager.checkPermission(SecurityManager.java:416)
at java.base/java.lang.SecurityManager.checkConnect(SecurityManager.java:919)
at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:620)
at java.base/sun.net.www.protocol.https.HttpsClient.<init>(HttpsClient.java:266)
at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:380)
at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1245)
at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1131)
at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179)
at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:142)
如果不设置安全管理器, 就能正常运行. 那为什么系统的默认安全管理器是能正常运行的呢? 因为在 System 类里面, 我们可以看到它的默认安全管理器是设置了允许所有的执行:
// s is the default SecurityManager
s.getClass().getProtectionDomain().implies(SecurityConstants.ALL_PERMISSION);