Spring源码--关于AccessController.doPrivileged

在Spring里发现一段代码,位置在,
DefaultListableBeanFactory -> preInstantiateSingletons()方法里。
如下:

if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
    isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
        public Boolean run() {
            return ((SmartFactoryBean<?>) factory).isEagerInit();
        }
    }, getAccessControlContext());
}

对于这个AccessController.doPrivileged,表示完全不懂是怎么回事。现在具体分析一下。

场景

当一个代码片段加载到现有的系统中,如果不对这个代码加以访问控制,那么它极有可能对当前系统安全造成破坏。比如这段代码会偷偷读取磁盘上的所有文件,然后保存到另外一台服务器上,造成机密信息泄露。
最常见的就是applet程序,它会被限制在一个沙箱里运行。或者导入其他jar包到系统,而这个jar包中的程序也有可能是恶意的。
所以,如果对这些代码进行权限控制是理所当然的。

安全管理器SecurityManager

作用是用于检查代码执行权限。其实日常的很多API都涉及到安全管理器,它的工作原理一般是:
1. 请求Java API
2. Java API使用安全管理器判断许可权限
3. 通过则顺序执行,否则抛出一个Exception

经典的FileInputStream,会调用SecurityManager的checkRead方法,用于检查是否有读取某个文件的权限。

public FileInputStream(File file) throws FileNotFoundException {
    String name = (file != null ? file.getPath() : null);
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkRead(name);
    }
    if (name == null) {
        throw new NullPointerException();
    }
    if (file.isInvalid()) {
        throw new FileNotFoundException("Invalid file path");
    }
    fd = new FileDescriptor();
    fd.incrementAndGetUseCount();
    this.path = name;
    open(name);
}

SecurityManager里的方法列表如下,

checkAccept(String, int)
checkAccess(Thread)
checkAccess(ThreadGroup)
checkAwtEventQueueAccess()
checkConnect(String, int)
checkConnect(String, int, Object)
checkCreateClassLoader()
checkDelete(String)
checkExec(String)
checkExit(int)
checkLink(String)
checkListen(int)
checkMemberAccess(Class<?>, int)
checkMulticast(InetAddress)
checkMulticast(InetAddress, byte)
checkPackageAccess(String)
checkPackageDefinition(String)
checkPermission(Permission)
checkPermission(Permission, Object)
checkPrintJobAccess()
checkPropertiesAccess()
checkPropertyAccess(String)
checkRead(FileDescriptor)
checkRead(String)
checkRead(String, Object)
checkSecurityAccess(String)
checkSetFactory()
checkSystemClipboardAccess()
checkTopLevelWindow(Object)
checkWrite(FileDescriptor)
checkWrite(String)

都是check方法,分别囊括了文件的读写删除和执行、网络的连接和监听、线程的访问、以及其他包括打印机剪贴板等系统功能。而这些check代码也基本横叉到了所有的核心Java API上。

访问控制器AccessController

上面的SecurityManager都是基于AccessController实现的,还是继续上面的FileInputStream。
security.checkRead()方法的细节,

public void checkRead(String file) {
    checkPermission(new FilePermission(file,
        SecurityConstants.FILE_READ_ACTION));
}

checkPermission()的细节,

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

可以看出,SecurityManager会调用AccessController里的方法。

概念

需要理解4个概念:代码源、权限、策略和保护域。

代码源CodeSource

CodeSource就是一个简单的类,用来声明从哪里加载类。

权限Permission

Permission类是AccessController处理的基本实体。Permission类本身是抽象的,它的一个实例代表一个具体的权限。权限有两个作用,一个是允许Java API完成对某些资源的访问。另一个是可以为自定义权限提供一个范本。权限包含了权限类型、权限名和一组权限操作。具体可以看看BasicPermission类的代码。典型的也可以参看FilePermission的实现。

策略Policy

策略是一组权限的总称,用于确定权限应该用于哪些代码源。话说回来,代码源标识了类的来源,权限声明了具体的限制。那么策略就是将二者联系起来,策略类Policy主要的方法就是getPermissions(CodeSource)和refresh()方法。Policy类在老版本中是abstract的,且这两个方法也是。在jdk1.8中已经不再有abstract方法。这两个方法也都有了默认实现。

在JVM中,任何情况下只能安装一个策略类的实例。安装策略类可以通过Policy.setPolicy()方法来进行,也可以通过java.security文件里的policy.provider=sun.security.provider.PolicyFile来进行。jdk1.6以后,Policy引入了PolicySpi,后续的扩展基于SPI进行。

保护域ProtectionDomain

保护域可以理解为代码源和相应权限的一个组合。表示指派给一个代码源的所有权限。看概念,感觉和策略很像,其实策略要比这个大一点,保护域是一个代码源的一组权限,而策略是所有的代码源对应的所有的权限的关系。

JVM中的每一个类都一定属于且仅属于一个保护域,这由ClassLoader在define class的时候决定。但不是每个ClassLoader都有相应的保护域,核心Java API的ClassLoader就没有指定保护域,可以理解为属于系统保护域。

使用方法

这是一个无法实例化的类——仅仅可以使用其static方法。

checkPermission

AccessController最重要的方法就是checkPermission()方法,作用是基于已经安装的Policy对象,能否得到某个权限。

doPrivileged

还有一个方法,就是doPrivileged()方法,获取特权,用于绕过权限检查。

参考

Java安全——安全管理器、访问控制器和类装载器
Java 授权内幕

    原文作者:Spring MVC
    原文地址: https://blog.csdn.net/laiwenqiang/article/details/54321588
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞