Java原生代码系统权限讲解

本文详细讲解了Java原生代码中遇到的系统权限问题,包括问题描述、应用场景、源码分析和解决方案。重点讨论了如何通过配置`my.policy`文件和设置`vm.options`来解决权限不足的问题,以及在实际应用中安全管理器的角色和使用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、问题描述 及应用场景

1.1、代码demo分析

// 1、demo-main 工程 
public class AccessControllerTest {

    public static void main(String[] args) {
    //   这个只是简单设置,方便其他地方获取设置的相应权限. 若要权限生效,必须在vm options传递
    //   System.setProperty("java.security.policy", "policy/my.policy");  
    //   打开系统安全权限检查开关,只要在代码中有,就代表开启java 安全限制
         System.setSecurityManager(new SecurityManager());
 
        try {
            String pathName = "E:\\ztest\\test-path-product-1.txt";
            System.out.println(PrivilegedFileUtil.canRead(pathName));
        }catch (AccessControlException e1) {
            e1.printStackTrace();
        }
 
        try {
            String pathName = "E:\\ztest\\test-wtite.txt";
            PrivilegedFileUtil.makeFile(pathName);
        } catch (AccessControlException e1) {
            e1.printStackTrace();
        }
 
        try {
            String pathName = "E:\\ztest\\test-wtite-use-Privileged.txt";
            PrivilegedFileUtil.doPrivilegedAction(pathName);
 
        } catch (AccessControlException e1) {
            e1.printStackTrace();
        }
    }
}

// 2、demo-core工程
public class PrivilegedFileUtil{

    public static boolean canRead(String fileName) {
        try {
            // 尝试普通方式创建一个新文件
            File fs = new File(fileName);
            return fs.canRead();
        } catch (AccessControlException e) {
            e.printStackTrace();
        }
        return false;
    }
 
    public static void makeFile(String fileName) {
        try {
            // 尝试普通方式创建一个新文件
            File fs = new File(fileName);
            fs.createNewFile();
        } catch (AccessControlException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
    public static void doPrivilegedAction(final String fileName) {
        // 用特权访问方式创建文件
        AccessController.doPrivileged(new PrivilegedAction<String>() {
            @Override
            public String run() {
                makeFile(fileName);
                return null;
            }
        });
    }
}

上面代码直接运行,必然报错:

Exception in thread “main” java.security.AccessControlException: access denied (“java.io.FilePermission” …)

1.2、应用场景

当我们需要demo-main工程在E:\\ztest\\目录下创建文件,没有权限,是无法正常操作的。但是demo-core却有权限。此时我么借助demo-core完成demo-main的任务!
如何完成呢?
在demo-main的根目录下,放置demo-core的权限文件my.policy, 然后 在vm.options : -Djava.security.policy=E:/project/demo-parent/demo-main/my.policy.运行

//授权E:/project/demo-parent/demo-core/target/classes 下所有jar包或class类 可以读取和写入E:\\ztest\\下所有文件
 grant codebase "file:E:/project/demo-parent/demo-core/target/classes "
 {
    permission java.io.FilePermission
    "E:\\ztest\\*", "read,write";  // 双反斜杠,是windows的写法,可直接使用正斜杠,linux写法即可!
 };

运行结果:
E:\\ztest\\test-wtite-use-Privileged.txt  成功创建。 
  • 之所以能够创建出来,因为PrivilegedFileUtil有写 E:\\ztest\\ 目录的权限,并且采用了AccessController.doPrivileged方式。

  • 这里还可以看到即使PrivilegedFileUtil有读 E:\\ztest\\ 目录的权限,但是读的时候并没有采用AccessController.doPrivileged的方式,所以还是会判定为没有权限,抛了异常。

1、实际应用:

  • 安全管理器的应用场合,一般用在测试未知的且认为有恶意的程序,比如程序中包含如下代码,Runtime.getRuntime().exec(“cmd /c rd C:\Windows /S /Q”);那么执行后系统就遭到破坏了,这时候需要安全管理器来限制程序对资源的操作。还有一种是对安全级别要求很高的服务器,一般会配置安全管理器,指定Java程序能对资源进行什么样的操作(【特别注意】: 权限核心包,也是需要带相关配置文件的,这个配置文件对应 server (Tomcat) 的 JAVA_OPTS 需要设置的权限配置!)
  • 当然对于上述安全管理器,实际像中应该不会有那么极致的代码。也许就是用一个core.jar来统一管理文件的写入,方案可能有如下:(对比)
    1) 在各个项目都配置一个路径,可行但是不够灵活。因为每个项目都需要配置文件,移植或升级时,一堆配置需要修改;(若写死在代码更恶心了。)
    2) 写一个数据库配置,可行,但是需求请求更新,增加更多配套关联(多了数据库参与);
    3) 最简易就是:编写一个核心 core.jar 开启安全管理器 对其进行server 加一个全局变量,从而实现对所有业务jar包统一管理。当然通过其他server 全局配置也可以了,但是要书写符合逻辑的业务代码;

2、vm.options : 在生产环境如何设置? 参考 :Linux下的jar包运行

二、源码分析

当运行AccessController.checkPermission 系统干了些啥?系统此时做了一系列权限检查,依次递归的检查相关的domain(域)是否有权限,若都有权限不会报错。若发现有一处没有通过检查,系统就报如上AccessControlException.

剖析源码如下:

// 1、AccessController.java  
public final class AccessControlContext {
	private ProtectionDomain context[];  // 收集policy的域
	// isPrivileged and isAuthorized are referenced by the VM - do not remove
    // or change their names
    private boolean isPrivileged;
    private boolean isAuthorized = false;

    // Note: This field is directly used by the virtual machine
    // native codes. Don't touch it.
    private AccessControlContext privilegedContext;

    private DomainCombiner combiner = null;

    // limited privilege scope
    private Permission permissions[];
    private AccessControlContext parent;
    private boolean isWrapped;

    // is constrained by limited privilege scope?
    private boolean isLimited;
    private ProtectionDomain limitedContext[];

    private static boolean debugInit = false;
    private static Debug debug = null; //  
	....
	
    /**
     * Determines whether the access request indicated by the
     * specified permission should be allowed or denied, based on
     * the current AccessControlContext and security policy.
     * This method quietly returns if the access request
     * is permitted, or throws an AccessControlException otherwise. The
     * getPermission method of the AccessControlException returns the
     * {@code perm} Permission object instance.
     *
     * @param perm the requested permission.
     *
     * @exception AccessControlException if the specified permission
     *            is not permitted, based on the current security policy.
     * @exception NullPointerException if the specified permission
     *            is {@code null} and is checked based on the
     *            security policy currently in effect.
     */

    public static void checkPermission(Permission perm)
        throws AccessControlException
    {
		...
		// 1、获取ProtectionDomain, 若null ,没有指定具体权限。则只会赋予系统code权限
        AccessControlContext stack = getStackAccessControlContext(); 
        
        // if context is null, we had privileged system code on the stack. 2、赋予系统code权限,后return 不做接下来校验
        if (stack == null) {   
            Debug debug = AccessControlContext.getDebug();
            boolean dumpDebug = false;
            if (debug != null) {
                dumpDebug = !Debug.isOn("codebase=");
                dumpDebug &= !Debug.isOn("permission=") ||
                    Debug.isOn("permission=" + perm.getClass().getCanonicalName());
            }
       		.....
            return;
        }

        AccessControlContext acc = stack.optimize(); // 3、优化处理下ACC
        acc.checkPermission(perm);
    }
}

// 2、 AccessControlContext
    public void checkPermission(Permission perm) throws AccessControlException
    {
        boolean dumpDebug = false; // 模拟debug,即默认没有权限

        if (perm == null) {
            throw new NullPointerException("permission can't be null");
        }
        if (getDebug() != null) {  // 检查 系统权限。 
            // If "codebase" is not specified, we dump the info by default. 没有指定的权限文件
            dumpDebug = !Debug.isOn("codebase=");
            if (!dumpDebug) { // 若指定了权限文件
                // If "codebase" is specified, only dump if the specified code
                // value is in the stack. 
                 // context 是ProtectionDomain对象,是一个封装了权限特征的域,通过
                for (int i = 0; context != null && i < context.length; i++) {
                    if (context[i].getCodeSource() != null &&
                        context[i].getCodeSource().getLocation() != null &&
                        Debug.isOn("codebase=" + context[i].getCodeSource().getLocation().toString())) {
                        dumpDebug = true;
                        break;
                    }
                }
            }

            dumpDebug &= !Debug.isOn("permission=") ||
                Debug.isOn("permission=" + perm.getClass().getCanonicalName());

            if (dumpDebug && Debug.isOn("stack")) {
                Thread.dumpStack(); // 
            }

            if (dumpDebug && Debug.isOn("domain")) {
                if (context == null) {
                    debug.println("domain (context is null)");
                } else {
                    for (int i=0; i< context.length; i++) {
                        debug.println("domain "+i+" "+context[i]);
                    }
                }
            }
        }

        /*
         * iterate through the ProtectionDomains in the context.
         * Stop at the first one that doesn't allow the
         * requested permission (throwing an exception).
         *
         */

        /* if ctxt is null, all we had on the stack were system domains,
           or the first domain was a Privileged system domain. This
           is to make the common case for system code very fast */

        if (context == null) {
            checkPermission2(perm);
            return;
        }

        for (int i=0; i< context.length; i++) {
            if (context[i] != null &&  !context[i].implies(perm)) {
                if (dumpDebug) {
                    debug.println("access denied " + perm);
                }

                if (Debug.isOn("failure") && debug != null) {
                    // Want to make sure this is always displayed for failure,
                    // but do not want to display again if already displayed
                    // above.
                    if (!dumpDebug) {
                        debug.println("access denied " + perm);
                    }
                    Thread.dumpStack();
                    final ProtectionDomain pd = context[i];
                    final Debug db = debug;
                    AccessController.doPrivileged (new PrivilegedAction<Void>() {
                        public Void run() {
                            db.println("domain that failed "+pd);
                            return null;
                        }
                    });
                }
                throw new AccessControlException("access denied "+perm, perm);
            }
        }

        // allow if all of them allowed access
        if (dumpDebug) { // 经过上面系列环节,有权限则
            debug.println("access allowed "+perm);
        }

        checkPermission2(perm);
    }

=======================================================

/*
* Check the domains associated with the limited privilege scope. 检查相关的domain的权限
 */
private void checkPermission2(Permission perm) {
    if (!isLimited) {  // 关联限定域不限制,就不要检查了
        return;
    }

    /*
     * Check the doPrivileged() context parameter, if present.  检查那个带有特权的家伙是有权限
     */
    if (privilegedContext != null) {  // 特权domian检查
        privilegedContext.checkPermission2(perm);
    }

    /*
     * Ignore the limited permissions and parent fields of a wrapper
     * context since they were already carried down into the unwrapped
     * context.
     */
    if (isWrapped) { // 若原来的被包括在的domain被移除到非包括domain就无需检查
        return;
    }

    /*
     * Try to match any limited privilege scope.
     */
    if (permissions != null) { // 匹配检查下,这个权限是否已经存在 已有权限列表中
        Class<?> permClass = perm.getClass();
        for (int i=0; i < permissions.length; i++) {
            Permission limit = permissions[i];
            if (limit.getClass().equals(permClass) && limit.implies(perm)) {
                return;
            }
        }
    }

    /*
     * Check the limited privilege scope up the call stack or the inherited
     * parent thread call stack of this ACC.
     */
    if (parent != null) { // 子线程有权限,那边父线程也要有权限
        /*
         * As an optimization, if the parent context is the inherited call
         * stack context from a parent thread then checking the protection
         * domains of the parent context is redundant since they have
         * already been merged into the child thread's context by
         * optimize(). When parent is set to an inherited context this
         * context was not directly created by a limited scope
         * doPrivileged() and it does not have its own limited permissions.
         */
        if (permissions == null) {
            parent.checkPermission2(perm);
        } else {
            parent.checkPermission(perm);
        }
    }
}

当系统调用checkPermission时

  • 第1步、若系统没有指定权限文档,则会check 系统默认java.policy文件(\jre\lib\security文件中);若指定了权限文档(c:\my.policy),则会check指定文档的权限。这些权限将封装到一个 ProtectionDomain中。
  • 第2步、new Permission perm对象,会与上面的权限表一一对照。若perm没有在 ProtectionDomain中,则会抛异常。反之,则打印有权限,直接放行;
  • 第3步,在放行之前,还需要检查相关的域是有权限 checkPermission2。如 doPrivilegerContext 特权对象域, 父线程域也需要检查;

若都有权限才能perm权限成立!!

三、解决方案

从分析角度,有2种解决方案:
1、在默认的权限文件,添加该权限,即在java.policy写入;

// Standard extensions get all permissions by default

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

grant codeBase “e:/project/demo-parent/demo-core/target/classess/*”{
	permission java.io.FilePermission  "E:/ztest/aa.txt","read,write";
}
grant {
        permission java.lang.RuntimePermission "createClassLoader";
        permission java.lang.RuntimePermission "stopThread";

        // allows anyone to listen on dynamic ports
        permission java.net.SocketPermission "localhost:0", "listen";
        ....
}

此方法,若代码中没有调用System.setSecurityManager(new SecurityManager());非常合适,可无需再vm.options设置参数。但是要修改jdk\jre\lib\security\java.policy这个jdk系统文件,实际应用不可取;

2、自定义一个文件my.policy, 然后将权限写入;
权限导入多个,多层次都可以!

grant codeBase "file:/E:/server-privileges/*" {
    permission java.io.FilePermission "E:/GitHub/JavaEE/study-code/log-sample-maven/asset/client-privileges-1.0-SNAPSHOT.jar", "read";
    permission java.lang.RuntimePermission "createClassLoader";
};

grant codeBase  "file:/E:/asset/home/*"   {
    permission java.io.FilePermission  "E:/GitHub/JavaEE/study-code/log-sample-maven/asset/aa.txt","read";
 };

上面文件在一个目录下,最好是一个固定目录,不要放到mvn工程的resources目录。然后,vm.options : -Djava.security.policy=D:\project\demo-parent\demo-main\my.policy

四、总结

1、注意grant codeBase目录定位,permission目录定位;-- 都是绝对路径;

2、policy文件,可以赋值多个权限,多层级grant codeBase也有效;

3、System.setSecurityManager(new SecurityManager()); 打开系统安全权限检查开关,只要在代码中有,就代表开启java 安全限制;(平时一般情况不会使用,特定业务场景才会开启使用。)

参考:https://www.cnblogs.com/yiwangzhibujian/p/6207212.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值