六,存取控制器

安全管理器大部分功能都是在存取控制器基础上实现的,可以说存取控制器是可以取代安全管理器的。


一般来说安全管理器是调用存取控制器的功能!


前面章节介绍的沙箱就是说的存取控制器,存取控制器就是沙箱的实现。回顾一下沙箱的基本要素,这些都是建立存取控制的基础

基本要素:

1,代码源

2,权限

3,策略:管理员可以设置策略文件,策略文件上面描述了所有的权限;在安全管理器中,策略对象表示为对所有权限的封装(内存)

4,保护域:利于权限的检索,指对某代码源相应权限的封装


我们先详细解读每一个要素的实现,然后再解释存取控制器是如何工作的


一,CodeSource类(java.security.CodeSource)  —————— 代码源

主要方法:

http://www.yq1012.com/api/java/security/CodeSource.html


二,权限类, Permission类

http://www.yq1012.com/api/java/security/Permission.html

1,权限的三个特性:类型、名、操作


注意:权限是类型的一个实例

如:在策略文件中定义一个权限对象

permission java.util.PropertyPermission "java.version", "read";

上面描述的权限在内存中的表示为:permission p = new java.util.PropertyPermission("java.version", "read");

可以看出对象是java.util.PropertyPermission类型的一个实例,构造时带名和操作参数。


对于我们这些普通java开发者,很少定义权限类,但是我们可以自定义权限类,只不过在实现”名、操作“的通配匹配上算法有点复杂,

另外,BasicPermission类提供了层次式通配匹配操作,我们可以直接继承或依赖他,也可以只继承Permission类来自行完成需要的匹配算法。


以下是参考书籍上的一个例子,

----此例子的名只是一个单值,并不是层次的,所以不需要复杂的匹配,操作只有2个,view和update,操作配置算法用或操作就完成,很简单

----如果名是层次的,要写路径覆盖算法,代码太多,这里只是简单的说明我们如何编写自己的权限类,

----注意equals(Object o),implies(Permission p) ,getActions() 核心方法的实现细节,他们都是抽象的,要自己实现的匹配逻辑

import java.security.*;
import java.util.*;

public class XYZPayrollPermission extends Permission {

    protected int mask;
    static private int VIEW = 0x01;
    static private int UPDATE = 0x02;

    public XYZPayrollPermission(String name) {
        // Our permission must always have an action, so we
        // choose a default one here.
        this(name, "view");
    }

    public XYZPayrollPermission(String name, String action) {
        // Our superclass, however, does not support actions
        // so we don't provide one to that.
        super(name);
        parse(action);
    }

    private void parse(String action) {
        // Look in the action string for the words view and
        // update, separated by white space or by a comma
        StringTokenizer st = new StringTokenizer(action, ",\t ");

        mask = 0;
        while (st.hasMoreTokens()) {
            String tok = st.nextToken();
            if (tok.equals("view"))
                mask |= VIEW;
            else if (tok.equals("update"))
                mask |= UPDATE;
            else throw new IllegalArgumentException(
                                    "Unknown action " + tok);
        }
    }

    public boolean implies(Permission permission) {
        if (!(permission instanceof XYZPayrollPermission))
            return false;
        
        XYZPayrollPermission p = (XYZPayrollPermission) permission;
        String name = getName();
        // The name must be either the wildcard *, which signifies
        // all possible names, or the name must match our name
        if (!name.equals("*") && !name.equals(p.getName()))
            return false;
        // Similarly, the requested actions must all match actions
        // that we've been constructed with.
        if ((mask & p.mask) != p.mask)
            return false;
        // Only if both the action and name match do we return true.
        return true;
    }

    public boolean equals(Object o) {
        if (!(o instanceof XYZPayrollPermission))
            return false;
        
        // For equality, we check the name and action mask.
        // We must provide a method definition like this, since
        // the security system expects us to do a deep check for
        // equality rather than relying on object reference
        // equality.
        XYZPayrollPermission p = (XYZPayrollPermission) o;
        return ((p.getName().equals(getName())) && (p.mask == mask));
    }

    public int hashCode() {
        // We must always provide a hash code for permissions,
        // because the hashes must match if the permissions compare
        // as equals. The default implementation of this method
        // wouldn't provide that.
        return getName().hashCode() ^ mask;
    }

    public String getActions() {
        // This method must return the same string, no matter how
        // the action list was passed to the constructor.
        if (mask == 0)
            return "";
        else if (mask == VIEW)
            return "view";
        else if (mask == UPDATE)
            return "update";
        else if (mask == (VIEW | UPDATE))
            return "view, update";
        else throw new IllegalArgumentException("Unknown mask");
    }

    public PermissionCollection newPermissionsCollection() {
        // More about this in a later example.
        return new XYZPayrollPermissionCollection();
    }

    public static void main(String[] args) {
        XYZPayrollPermission p1 = new XYZPayrollPermission("sdo", "view");
        XYZPayrollPermission p2 = new XYZPayrollPermission(args[0], args[1]);
        System.out.println("P1 is " + p1);
        System.out.println("P2 is " + p2);
        System.out.println("P1 -> P2 is " + p1.implies(p2));
        System.out.println("P2 -> P1 is " + p2.implies(p1));
    }
}


2,权限集(抽线类):PermissionCollection类,某一个权限类的所有实例的集合,用途:1-存储权限;2-检查是否包含指定权限

3,权限集集合:Permissions类,他继承权限集PermissionCollection,和权限集表现一样的接口,因此作用一样,只不过他更强,他可以存储多个不同类型的权限集,也可用于不同类型权限的检索


以下是参考书籍上的一个例子,

import java.util.*;
import java.security.*;
import java.util.*;

public class XYZPayrollPermissionCollection extends PermissionCollection {
    private Hashtable permissions;
    // We keep track of whether the * name has been added to make
    // the implies method simpler.
    private boolean addedAdmin;
    private int adminMask;

    XYZPayrollPermissionCollection() {
        permissions = new Hashtable();
        addedAdmin = false;
    }

    public void add(Permission p) {
        // Required test
        if (isReadOnly())
            throw new IllegalArgumentException("Read only collection");

        // This is a homogenous collection, as are all 
        // PermissionCollections that you'll implement.
        if (!(p instanceof XYZPayrollPermission))
            throw new IllegalArgumentException("Wrong permission type");

        XYZPayrollPermission xyz = (XYZPayrollPermission) p;
        String name = xyz.getName();
        XYZPayrollPermission other = 
                       (XYZPayrollPermission) permissions.get(name);
        if (other != null)
            xyz = merge(xyz, other);
        // An administrative permission. The administrative permission
        // may have only view or only update or both, and multiple
        // admin permissions may be added, so the masks are OR-ed
        // together.
        if (name.equals("*")) {
            addedAdmin = true;
            adminMask = xyz.mask | adminMask;
        }
        permissions.put(name, xyz);
    }

    public Enumeration elements() {
        return permissions.elements();
    }

    public boolean implies(Permission p) {
        if (!(p instanceof XYZPayrollPermission))
            return false;
        XYZPayrollPermission xyz = (XYZPayrollPermission) p;
        // If the admin name is present, then all names are implied;
        // we need check only the permissions.
        if (addedAdmin && (adminMask & xyz.mask) == xyz.mask)
            return true;
        // Otherwise, we can just see if the given individual is
        // in our table and if so, see if the permissions match.
        Permission inTable = (Permission) permissions.get(xyz.getName());
        if (inTable == null)
            return false;
        return inTable.implies(xyz);
    }

    // This is called when the same name is added twice to the
    // permissions; we merge the action lists and only store the
    // name once.
    private XYZPayrollPermission merge(XYZPayrollPermission a,
                                       XYZPayrollPermission b) {
        String aAction = a.getActions();
        if (aAction.equals(""))
            return b;
        String bAction = b.getActions();
        if (bAction.equals(""))
            return a;
        return new XYZPayrollPermission(a.getName(), aAction + "," + bAction);
    }
}

三,Policy类  策略


权限集要和代码源相关联,之后我们才能判断出代码具有什么样的权限。策略类定义了这种关联。

如果要自己写策略,

----就需要解读策略文件,或者权限存于数据库,你需要自己的一套解读方式

----需要为代码源关联权限集,注意权限集的创建并不是由策略完成的,策略只是保存数据,创建权限的工作由运行时的类加载器搞定

----策略类需要读文件或网络的权限,往往是特权,特权在本文后面介绍


其实我在读完存取控制后,就开始担心java安全策略的效率,但等到读完类加载器后,这些顾虑都没有了,策略对象里面代码源的权限集并不是启动java的时候就生成的,而是由类加载器实时完成的。

所以我们并不用担心JAVA虚拟机安全的处理速度,更不用担心检索速度(内存速度)


例子:

例子展示了策略对象的主要功能

----存储权限 (具体的权限由类加载器生成,然后抛给策略对象的)

----检索代码基的权限集

import java.security.*;
import java.util.*;

public class MyPolicy extends Policy {

    // This inner class defines a simple set of permissions:
    // either everything is allowed (the implies() method always
    // returns true, and the collection contains an AllPermission
    // object) or everything is prohibited (the implies()
    // method always returns false and the collection is empty).
    static class SimplePermissions extends PermissionCollection {
        boolean allow;
        Permissions perms;

        SimplePermissions(boolean b) {
            allow = b;
            perms = new Permissions();
            if (allow)
                perms.add(new AllPermission());
        }

        public void add(Permission p) {
            if (isReadOnly())
                throw new SecurityException(
                             "Can't add to this collection");
            perms.add(p);
        }

        public Enumeration elements() {
            return perms.elements();
        }

        public boolean implies(Permission p) {
            return allow;
        }
    }

    // We never change the policy
    public void refresh() {}

    //If the code was loaded from a file, return a collection
    // that implies all permissions. Otherwise, return an
    // empty collection (one that implies no permissions).
    public PermissionCollection getPermissions(CodeSource cs) {
        if (cs.getLocation().getProtocol().equals("file"))
            return new SimplePermissions(true);
        return new SimplePermissions(false);
    }
}

在虚拟机中,任何时候只有一个策略对象,

我们可以替换系统的策略类,通过程序化或者管理手段配置

程序化: setPolicy(Policy p)

管理手段:

---java.security中 plicy.provider=sun.security.provider.PolicyFile 替换为自己的类

---把自己的类放如rt.jar中,或者在运行虚拟机时正确适合参数以指定引导路径 java -Xbootclasspath:/files/policyfile  ...


四,保护域 ProtectionDomain

前面谈过Permissions对象——权限集集和,他和某一个代码基关联起来就是保护域

也就是说,他存储了某个代码基的所有权限集集和

具体核心方法请参考API文档 


保护域对开发人员是透明的,我们不需要了解他的API细节,

但我们要知道,每一个java类都属于且仅属于一个保护域,定义类时,保护域可以来自默认的安全模型(策略文件),也可由类加载器拓展(或替换)。



 

五、存取控制器  AccessController


前面介绍了存取控制器的要素,存取控制器没有实例,构造函数私有,有一组静态方法用于确定是否得到权限以实现某个操作


例子演示了他简单的用法

<span style="font-size:12px;">import java.applet.*;
import java.net.*;
import java.security.*;

public class AccessTest extends Applet {
    public void init() {
        SocketPermission sp = new SocketPermission(
                            getParameter("host") + ":6000", "connect");
        try {
            AccessController.checkPermission(sp);
            System.out.println("Ok to open socket");
        } catch (AccessControlException ace) {
            System.out.println(ace);
        }
    }
}</span>

1,存取控制是如何工作的——如何检索权限的

----存取控制器方法调用后,存取控制开始工作

----存取控制器检索堆栈中最后压入栈的类,存取控制类除外,检索类代码基是否有相应权限,以上代码是检索是否有套接字权限,host:6000

----有权限,就检测堆栈前一个类,直到堆栈完毕。

----没有权限,就抛出运行时异常,线程完毕。


来自于不同加载器和不同签名jar包的类,由加载器设置权限集合,所以类加载器为了安全考虑,必须首先检索父加载器是否加载了类,


这里不能画图 唉!


2,特权接口,PrivilegedAction 和 PrivilegedExceptionAction 接口

实现特权接口的类,就是特权类,只有一个方法就是run(); 


如果我们把一段程序封装在特权类中,我们就能让存取控制器网开一面,阻止存取控制递归检索堆栈中的权限

话题:A类有套接字locahost:6000权限,B类没有,如果B调用A,那么网络访问就会失败,但这种情况下,我们希望A能顺利访问网络,怎么办呢?

解答:特权类

           我们改造类A,让A访问网络的代码封装在特权类中,这样不管调用A的类是否具备套接字权限,A的对象都能顺利访问网络


<span style="font-size:12px;">import java.net.*;
import java.io.*;
import java.security.*;

public class A {
    public A() {
        try {
            // This class is used by the doPrivilged() method to
            // open a socket
            class doSocket implements PrivilegedExceptionAction {
                public Object run() throws UnknownHostException,
                                     IOException {
                    return new Socket("localhost", 6000);
                }
            };
            doSocket ds = new doSocket();
            Socket s = (Socket) AccessController.doPrivileged(ds);
        } catch (PrivilegedActionException pae) {
            Exception e = pae.getException();
            if (e instanceof UnknownHostException) {
                // process host exception
            }
            else if (e instanceof IOException) {
                // process IOException
            }
            else {
                // e must be a runtime exception
                throw (RuntimeException) e;
            }
        }
    }
}</span>

我们看看例子堆栈的情况

-----new Socket("localhost",6000) 会在内部调用存取控制器, 存取控制器会递归堆栈检索指定权限 :new java.net.SocketPermission("localhost",6000)

-----存取控制先检索类核心API  Socket 是否有套接字权限

------有,继续检索doSocket类套接字权限

-----doSocket为特权类,他会设置断点,阻止存取控制继续检索,从而返回,

-----存取控制器返回“真”,套接字访问顺利完成


如果A类的对象,被任何其他类型的对象调用,A都能有特权访问套接字了


例子的分析,大家可能有疑惑,我们更进一步分析下

-----程序执行首先要进堆栈,然后出栈,

-----语句AccessController.doPrivileged(ds),会让ds.run靠后进栈,之后让new Socket("localhost",6000)最后进栈,也就是说存取控制器回调run,

-----new Socket("localhost",6000)会进一步让AccessController.checkPermission()方法进栈,checkPermission()才是检索类权限的真正指令流

-----等checkPermission()执行完毕后,知道有权限才让new Socket("localhost",6000)正常返回,否则,如果没有权限,抛出运行时异常,堆栈结束了

-----AccessController.doPrivilleged()是很早进入栈的,但他进栈时,在栈中设置了一个断点,断点会阻止checkPermission()方法中检索代码的执行,

-----因此checkPermission()执行中途,检测到堆栈断点,返回,不会去检索栈中其他类的权限了,从而达到了特权的目的。


没法画图,唉







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值