蹲厕所的熊

benjaminwhx

AccessController.doPrivileged

2018-06-19 作者: 吴海旭


  1. 前言
  2. Java中的安全模型
  3. 使用

前言

上一篇文章我们说到了SecurityManager,它主要是通过配置策略文件来进行授权,最后的授权操作调用的是AccessController的checkPermission方法:

public void checkPermission(Permission perm) {
    java.security.AccessController.checkPermission(perm);
}

看了一下API,我们发现AccessController有很多以doPrivileged开头的方法,查看了一下调用发现JDK很多源码都调用了它:

// 代码来自java.net.URLClassLoader
public URL findResource(final String name) {
    /*
     * The same restriction to finding classes applies to resources
     */
    URL url = AccessController.doPrivileged(
            new PrivilegedAction<URL>() {
                public URL run() {
                    return ucp.findResource(name, true);
                }
            }, acc);

    return url != null ? ucp.checkURL(url) : null;
}

这种包装一层在内部使用run方法来执行操作的方式到底有什么作用呢?

Java中的安全模型

在 Java 中将执行程序分成本地和远程两种,本地代码默认视为可信任的,而远程代码则被看作是不受信的。对于授信的本地代码,可以访问一切本地资源。而对于非授信的远程代码在早期的 Java 实现中,安全依赖于沙箱 (Sandbox) 机制。沙箱机制就是将 Java 代码限定在虚拟机 (JVM) 特定的运行范围中,并且严格限制代码对本地系统的资源访问,通过这样的措施来保证对远程代码的有效隔离,防止对本地系统造成破坏。

但如此严格的安全机制也给程序的功能扩展带来障碍,比如当用户希望远程代码访问本地系统的文件时候,就无法实现。因此在后续的 Java1.1 版本中,针对安全机制做了改进,增加了安全策略,允许用户指定代码对本地资源的访问权限。

在应用开发中存在很多关于安全的复杂用法,比如最常用的AccessController类的doPrivileged。该方法能够让执行的一段授权方法获得更大的权限(能让跟多的调用方也得到授权),也就是“特权”的含义,在一些特殊场景中非常有用。例如,应用程序可能无法直接访问某些系统资源,但这样的应用程序必须得到这些资源才能够完成功能。针对这种情况,Java SDK提供了doPrivileged方法来让程序突破当前域权限的限制,临时扩大访问权限。下面我们来讲解一下该方法如何使用。

使用

首先我们要在项目a中创建一个类 FileUtil ,用来提供特权创建文件方式和直接创建文件两种方式:

public class FileUtil {
    // 工程 A 执行文件的路径
    private final static String FOLDER_PATH = "/Users/Benjamin/Desktop";

    public static void makeFile(String fileName) {
        try {
            // 尝试在工程 A 执行文件的路径中创建一个新文件
            File fs = new File(FOLDER_PATH + "/" + fileName);
            fs.createNewFile();
        } catch (Exception e) {
            // ignore
        }
    }

    public static void doPrivilegedAction(final String fileName) {
        // 用特权访问方式创建文件
        AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
                makeFile(fileName);
                return null;
            }
        });
    }
}

接着,我们把该项目打成一个jar包供其他项目使用,并把这个jar包放在 /Users/Benjamin/Desktop 下。然后改变默认策略文件的内容(这里我们使用Java自带的策略文件:${java.home}/jre/lib/security/java.policy)

// 授权工程 A 执行文件路径中文件在本目录中的写文件权限
// https://docs.oracle.com/javase/7/docs/technotes/guides/security/PolicyFiles.html
// /-代表目录下所有目录的所有文件,/*仅仅代表目录下这一层级的所有文件
grant codebase "file:/Users/Benjamin/Desktop/-" { 
    permission java.io.FilePermission "/Users/Benjamin/Desktop/*", "write"; 
};

这段配置的意思是允许 a.jar/Users/Benjamin/Desktop 这个目录下写文件。

接着,我们另起一个项目b,并引入jar包 a.jar ,调用 FileUtil

public class AccessControllerTest {

    public static void main(String[] args) {
        // 打开系统安全权限检查开关
        System.setSecurityManager(new SecurityManager());

        // 1. 用特权访问方式在工程 A 执行文件路径中创建 temp1.txt 文件
        FileUtil.doPrivilegedAction("temp1.txt");

        // 2. 用普通文件操作方式在工程 A 执行文件路径中创建 temp2.txt 文件
        try {
            File fs = new File("/Users/Benjamin/Desktop/temp2.txt");
            fs.createNewFile();
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 3. 直接调用普通接口方式在工程 A 执行文件路径中创建 temp3.txt 文件
        FileUtil.makeFile("temp3.txt");
    }
}

执行main方法,我们会发现桌面只生成了 temp1.txt 文件,而其他文件没有生成,错误信息为:

java.security.AccessControlException: access denied ("java.io.FilePermission" "/Users/Benjamin/Desktop/temp2.txt" "write")
    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 java.lang.SecurityManager.checkWrite(SecurityManager.java:979)
    at java.io.File.createNewFile(File.java:1008)
    at com.github.security.AccessControllerTest.main(AccessControllerTest.java:28)
java.security.AccessControlException: access denied ("java.io.FilePermission" "/Users/Benjamin/Desktop/temp3.txt" "write")
    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 java.lang.SecurityManager.checkWrite(SecurityManager.java:979)
    at java.io.File.createNewFile(File.java:1008)
    at com.github.web.FileUtil.makeFile(FileUtil.java:22)
    at com.github.security.AccessControllerTest.main(AccessControllerTest.java:34)

也就是说,如果一个应用开启了安全管理,其他应用想要访问一些授权方法时,必须使用doPrivileged方法开启特权才行。



坚持原创技术分享,您的支持将鼓励我继续创作!



分享

评论