OSGi R4服务平台核心规范 :第九章 条件权限管理规范(2)

 由  满江红开放技术研究组织 发布

9.5.权限检查

Java 2安全模型同时拥有安全管理和访问控制来实现权限检查。其核心功能集中在访问控制类(AccessController)和访问控制上下文(AccessControlContext)类中,同时这两个类与检查是否已经授予权限的类保护域(ProtectionDomain)和权限(Permission)进行配合使用。保护域(ProtectionDomain)对象中存有很多权限类。在OSGi框架中,bundle必须拥有一个单独的bundle保护域对象。

访问控制类提供了对权限检查的所有功能。但是,为了向后兼容,所有的系统检查都是通过 SecurityManager对象的checkPermission方法进行。也可以使用定制方式,而不是访问控制类(是一个final类)来实现安全管理。在图45种描述了这种模型。

条件权限管理通过一系列条件来对权限控制。只有当符合条件时才会授予权限。Conditions会请求用户交互,并导致其他影响。其中一个后果就是这种执行条件的处理模型必须是高度优化的。这也就要求框架必须完成权限检查。因此,框架必须利用条件权限管理的一种实现来替代安全管理。

如果由于其他部分已经安装了,导致框架实现不能替代安全管理器(Security Manager)。那么并不是本规范的所有特性都可以实现。

【图9.5.1】

9.5.1.权限检查算法

当安全管理器使用权限P为参数来调用checkPermission方法时,就开始了权限检查。框架必须实现安全管理器,称之为框架安全管理器(Framework Security Manager)。并且必须将它完全和条件权限管理服务(Conditional Permission Admin service)集成。

框架安全管理器必须有效获得访问控制上下文(Access Control Context)。如果没有对它指定一个上下文,那么必须调用AccessController. getContext()方法来获得一个默认的上下文。

然后必须调用对象AccessControlContext的checkPermission方法,这个方法将对调用栈进行遍历。在每一个栈级别上,使用ProtectionDomain对象的隐含方法对权限P计算调用类的bundle保护域。计算的过程必须在同一个线程中进行。

在Bundle Protection Domain的局部权限中必须隐含了权限P。如果没有,那么结束检查过程,检查结果为失败。在局部权限一节和bundle权限资源一节中描述了局部权限。

Bundle Protection Domain必须要确定在例示权限表中哪些元组是适用的,并且是隐含了P的。 因此,它必须要执行以下步骤,或者是通过其他替代方法来获得同样的结果:

  • 对于例示权限表格中的每一个元组T:
  • 如果T中有立即条件式,计算所有的立即条件式对象,如果有任何一个条件对象不满足,那么计算下一个元组。
  • 如果T的权限中没有隐含P,继续下一个元组。
  • 如果T中含有延迟的条件对象,那么延迟计算T,转到下一个元组。
  • 否则,移除所有的延迟对象,返回true。
  • 当所有的元组都已经处理过之后
  • 如果还有任何延迟对象,那么返回true,否则返回false。
在图46中描述了算法的流程:

在框架安全管理器调用访问控制上下文的checkPermission方法之后,必须确定是检查失败还是处理延迟计算的元组。如果方法返回false,那么框架安全管理器的checkPermission方法检查失败。

如果返回true,那么还应该处理延迟计算的元组。这些元组中都蕴含了权限P,否则就不会将它们放在延迟计算元组集合中。但是,在框架安全管理器的checkPermission方法返回true之前,还要对它们的条件进行检查确定是否满足。

延迟计算的元组集合并不是一个线性的列表。很多的bundle保护域(Bundle Protection Domains)都和这个列表有关。而每一个bundle保护域必须蕴含了需要的权限,因此,对于每一个bundle保护域,至少要能找到一个元组来满足,这样框架安全管理器的checkPermission方法才可以返回true。

例如,在延迟计算列表中,T1是bundle A的,而T2是bundle B的,那么必须要同时满足T1和T2。但是,如果T1和T2都是bundle A的,那么只需要满足T1或者T2就可以返回true。图47描述了这个示例:

条件式的计算必须是成组进行的,这样相同实现的Condition对象就可以一起计算。例如,如果需要用户命令输入,那么就有必须移除复制的条件式,并且所有的问题都只询问一次。

可以通过方法isSatisfied(Condition[], Dictionary)来计算多条件式。这个方法是一个接口中的方法,应该实现这个方法。Condition类使用这个集合来移除复制的条件式,移除覆盖的条件式,将需要用户交互的放置到一组中。这个方法可以使用语义来判断集中计算的条件式的结果是true还是false。

在框架安全管理器的checkPermission方法调用期间,Condition的实现类对象使用方法isSatisfied(Condition[], Dictionary)中的Dictionary参数来保存状态信息。Dictionary对象的作用:

  • 是Condition实现类的一个特例,不同的实现类不能共享这样的Dictionary对象
  • 在方法isSatisfied(Condition[],Dictionary)第一次调用之前创建。
  • 在调用一个单独的权限检查会话中有效。也就是说,在不同的权限检查调用期间没有保存Dictionary对象。
  • 在不同的bundle保护域之间的isSatisfied(Condition[], Dictionary)是共享的。

框架安全管理器必须对每一个bundle保护域找到至少一个元组来满足。也可能是每一个bundle保护域有多个元组,那么只需要满足其中一个元组就可以了。在方法isSatisfied(Condition[],Dictionary)中,必须要对所有的条件进行检查确定是否满足,不允许在条件之间使用OR操作。因此,只能在不同的bundle保护域之间的元组进行成组计算。也就是说,分组必须是使用每一个bundle保护域中的一个条件元组。

在图48中,每一个bundle保护域都有两个元组是延迟计算的。框架安全管理器必须要将这些元组分成序列: (T1,T3,T5)、(T2,T3,T5)、 (T1,T4,T5)、(T2,T4, T5)、 (T1, T3, T6)、 (T2,T3, T6)、(T1, T4, T6)、(T2, T4, T6) ,并且计算每一个序列。对于每一个序列,使用方法isSatisfied(Condition[],Dictionary)来计算所有的条件式对象。计算顺序可以有不同实现,而且可以进行优化。

框架安全管理器的checkPermission方法的整个执行算法如图49所示:

9.5.2.示例  ### 

当bundle A、B和C都在调用栈中时,检查权限P。权限配置过程如下(IC = 立即计算的条件,PC为延迟计算的条件,P、Q和R表示权限。元组已经预处理,只和给定bundle匹配。也就是,已经计算并清除了所有的Bundle*Condition对象):

首先,如果查询bundle C的保护域对象中是否蕴含权限P。C中有三个元组。第一个元组没有条件表达式,只有一个不蕴含权限P的权限表达式。

第二个元组中有一个不满足的立即条件式IC0,因此,不考虑这个元组的权限表达式。

最后一个元组包含了一个延迟计算元组PC2,在它的权限表达式中蕴含了权限P。这时候还不可能做出判断,因此元组C3的计算延迟进行。但是,还是返回true表示这个bundle是潜在允许的。

接下来,考虑bundle B。在第一个元组中,有立即条件对象IC1。这个条件是满足的。将抵一个元组归入潜在候选是由于元组中还有两个延迟计算的条件。因此还是有必要检查这个元组是否是一个可能结果。而且由于权限表达式中蕴含权限P,那么这个元组放置到延迟计算列表中。

第二个元组也是类似的。由于它蕴含了权限P,而且有一个延迟计算条件式PC2,因此也必须将它放置到延迟计算列表。

对第三个元组的判断结果是拒绝的,这是由于它没有蕴含权限P。但是,由于存在两个延迟计算元组,因此这个bundle也是潜在允许的。

对于上述例子,首先计算bundle A的IC1表达式,并且结果是满足的。而在A的权限表达式中蕴含了权限P,因此延迟计算这个元组。但是,第二个元组的立即条件表达式也满足,而且蕴含了P,所以,对删除对第一个元组的延迟计算,而是直接就允许bundle A,同时也不必考虑了第三个元组。

调用访问控制上下文的checkPermission方法之后,框架的安全管理器必须要计算所有的延迟计算元组。延迟计算元组如图50所示:

现在框架安全管理器必须计算排列 (T1,T3) 或者 (T3,T2)。PC2的条件类同时在两个元组中出现,因此,必须将T1:PC2和T3:PC2组成一个带有版本标记的组来使用isSatisfied(Condition[], Dictionary)方法计算。而PC1是必须匹配的,它的计算是使用不带组标记的isSatisfied()方法。

长远来说,假设排列(T1, T3)的计算结果返回为失败,虽然对用户进行了交互而且用户同意了权限,那么PC1的返回结果还是失败。而在PC2中的Dictionary对象上增加标记防止再次询问用户同样的权限确认问题。

然后,框架安全管理器尝试序列(T2,T3)。必须通过方法isSatisfied(Condition[],Dictionary)来计算T2:PC2和T3:PC2。在上一个排列的计算中已经调用了这个方法,而且在Dictionary对象中保留了计算信息。而且,由于Dictionary对象中保留了原来调用的标记。再次调用时,方法发现了这个标记,从而直接返回true而不是再次询问用户。也就是说序列(T2,T3)是匹配的,从而框架安全管理器的checkPermission方法返回结果为成功。

9.5.3.直接使用上下文访问控制

bundle的开发人员可以直接使用标准Java API来完成安全检查。但是,如果直接使用访问控制器(Access Controller)来代替安全管理器进行安全检查(或者是使用访问控制上下文,Access Control Context),那么就不能处理延迟判断的条件式。因此,在这种情况下,bundle保护域必须将延迟条件式看作是立即条件式一样同等处理。

上文中隐含了这样一个事实:权限检查的结果依赖于检查的实现策略。一般说来,安全管理器的checkPermission方法可访问控制上下文的checkPermission方法的执行过程是不一样的,依赖于运行的检查策略。

例如,栈中的一个bundle需要权限P,而P和一个用户提示条件式相关,而另一个bundle没有权限P。如果是调用安全管理器,那么检测结果是失败的,而且不会提示用户进行交互。如果直接调用访问控制上下文,那么用户将在返回检测结果为失败之前获得提示来交互。

9.5.4.优化

理论上,每一个checkPermission方法必须要计算调用栈中的每一个bundle。也就是,矿井安全管理器必须遍历栈中的所有bundle,对这个bundle检查权限表中的每一个元组,计算所有的条件式,测试所有的权限式,直到找到了隐含的权限。这样的模型的计算代价是非常大的。

因此,框架实现者必须要尽可能的优化计算的过程。他们可以修改本规范描述的算法而只要保持外在的表现是一致的。

第一步的优化就是对权限表的裁剪。如果条件是固定的那么这样的条件对象是可以裁剪的。

如果满足一个固定的条件对象,那么就可以移除这个条件对象,这是由于它对计算结果不会再有影响。如果不满足,那么就可以不管这样的条件式,这是由于不满足这个条件式,使得元组也是不可能用的。

如果裁剪后元组中不存在其他元组,那么就可以将所有的这样的权限集中到一个权限对象中(包括了PermissionCollection对象)。那么Java 2安全机制中就有高度优化的代码来进行权限检查了。

例如,假设有以下权限表格:

假设这个表格是通过一个来自http://www.acme.com/bundle.jar的bundle而初始化的。第一个元组的权限可以使用一个特定的权限集合来代替,这是由于Bundle Location的条件是固定的,而且满足这个条件。

而第二个元组可以删除,这是由于它的条件式是永远都不会满足的。

9.6.权限管理

系统权限通过条件权限管理服务(Conditional Permission Admin service)来进行管理。条件权限管理也可以注册权限管理服务,这种情况下,这两种服务的相互作用必须根据下文中与权限管理的关系一节中所描述的方式。

通过使用方法addConditionalPermissionInfo(ConditionInfo[],PermissionInfo[])或者方法setConditionalPermissionInfo(String,ConditionInfo[],PermissionInfo[])来将权限添加到元组的权限编码中。这两种方法的不同在于参数名称的不同。

可以将ConditionalPermissionInfo对象匿名或者自命名添加权限。每一个ConditionalPermissionInfo对象都具有一个惟一名称用于区分其他ConditionalPermissionInfo对象,同时也将它和一个管理服务器区分开来。如果没有指定名称或者是名称为空,那么条件权限管理服务将自动为它创建一个名称。

上述方法都是返回一个ConditionalPermissionInfo对象,可以通过这个对象来访问元组。这些对象也可以通过方法getConditionalPermissionInfos()来进行枚举访问,或者是指定一个名称来调用方法getConditionalPermissionInfo(String)访问。可以通过方法getName()获得返回对象的名称。可以通过方法ConditionalPermissionInfo.delete()来删除一个ConditionalPermissionInfo对象。在元组中包含了一个ConditionInfo对象数组和一个PermissionInfo数组,如图51所描述的:

ConditionalInfo对象和PermissionInfo对象都可以通过编码字符串来组建。字符串的编码格式如下:

http://assets.osgi.com.cn/article/7289466/18.jpg

编码中不考虑多余的空格符号。

作为一种约定,ConditionalPermissionInfo对象是包含在花括号中的:(’{}’, \u007B, \u007D)。而且没有编码和解码的方法调用。下面的例子是读取按照约定包含在花括号中的条件权限流的代码片段。解析是一行一行的解析处理。删除每一行中多余的空格,然后和控制字符相比较。如果找到了匹配的的‘}’那么也就设置完成了当前的条件和权限。

9.6.1.缺省权限

条件权限管理中没有缺省权限的情况。缺省权限是从不包括任何条件表达式对象的ConditionalPermissionInfo对象中导出的。这些ConditionalPermissionInfo对象蕴含了所有的bundle,可以有效的处理缺省权限。这是和权限管理(Permission Admin)中不一样的地方;在权限管理中,只有当没有指定权限集合时才会使用缺省权限。

9.7.条件式

条件式的目的在于确定是否应有一个权限集合。也就是说扮演着守卫的角色。因此,当对照bundle的有效权限来检查权限对象时必须计算条件。

Condition对象可以通过它的方法isSatisfied()来获得对象的状态。如果返回true,那么就称之为满足条件,如果抛出了异常,那么应该在日志中记录异常信息,而且将这种情况看作是不满足条件的情况。

如果在同一个权限检查中某个特定的Condition对象重复进行了多次计算,那么可以将这个过程进行优化。例如,在权限检查中,可能需要多次进行用户提示确认,而用户的输入最好的一次完成。这些条件式称之为延迟条件式(postponed conditions)。可以立即得出值的条件式称之为立即条件式(immediate conditions)。可以通过方法isPostponed()来判断一个条件式是否需要延迟计算。。如果方法返回false,那么就可以马上调用isSatisfied方法来进行判断,否则,只能等到权限检查的最后阶段调用isSatisfied方法。

例如,一个条件式是用于判断移动电话是否处于漫游状态的。可以很容易在内存中得到这些信息,那么方法isPostponed()通常是返回false。对应的,一个通过网络获得授权的条件式应该在权限检查时最多只进行一次计算。对于这样的条件式isPostponed方法应该返回true,以便于在权限检查的最后阶段来统一进行这些Condition对象的验证。

只有当条件式的答案是变化的时候才需要对Condition对象进行多次计算。这种Condition对象的可满足性是可变化的特性称之为易变性,可以通过方法isMutable()来检查这个特性。如果条件式结果是固定的,那么条件权限管理就可以通过对权限表中的元组进行剪枝来获得显著的优化。例如,bundle保护域可以将权限表中的包含了不满足的立即条件对象的元组移除。由于,这样的条件式是不可能满足的,因此在bundle保护域中可以不管这样的权限元组。

这种优化是由提供的BundleLocationCondition和BundleignerCondition对象共同决定。在保护域中不会考虑和保护域的bundle不匹配的条件权限。但是,Condition对象也许启动的时候是可变的,而以后可以变成是固定的。例如,一个用户命令行提示可能具有以下状态:

  • Prompt – 必须要对用户进行提示以获得答案,然后条件权限管理来根据答案判断是否满足条件。
  • Blanket – 由于用户在以前的提示中指出了以后遇到同样的提示也采用同样的处理方式。在这种状态下,Condition对象是固定不变的。

对于指定bundle,规范提供了一系列的Condition对象来绑定权限。但是,客户化的代码也可以提供条件式。下文中描述了怎样创建条件类,并定义了标准的Condition类。

9.7.1.客户化条件式

如果根据bundle保护域来初始化权限表,那么Condition对象是从ConditionInfo对象中构建的。ConditionInfo对象支持一系列参数。框架的安全管理器必须要通过反射来从实现了Condition的类中查找一个公有的静态getCondition方法,这个Condition的实现类有两个属性:一个Bundle对象和一个ConditionInfo对象。getCondition方法返回一个实现了Condition接口的对象。

但是,并不是要求这必须是一个新对象,如果需要,getCondition方法也可以重用对象。例如,有一个Bundle Location Condition对象是不易变的,因此只由两个实例对象:一个分配给与指定位置匹配的bundle,另一个分配给其他bundle。这种情况下,通过比较bundle的位置和给定的参数信息来确定返回哪一个实例对象。

Condition接口提供这样两种实例的模式是一种非常常见的模式。

  • TRUE –计算结果通常为true,而且从来不会延迟计算的条件对象。
  • FALSE – 计算结果为false,而且从来不会延迟计算的条件对象。
如果没有找到静态的getCondition方法,那么条件权限管理服务必须要尝试找到一个公有的使用一个Bundle对象和一个ConditionInfo对象作为参数的构造方法。条件权限管理必须查找:

如果不能创建一个条件对象,那么必须将给定的条件对象看作是一个Condition.FALSE对象,而且在日志中记录一个错误。

对于一个bundle保护域来说,Condition对象是惟一的,在实例编码一节中进行了解释。因此,在Condition对象上的任何查询将以给定的Bundle作为上下文。如果框架安全管理器(Framework Security Manager)不能够找到一种合适的方法来构造Condition对象,那么应该记录这个事件,而且要确保条件结果为false。

另一个需要考虑的方面是计算的时间和易变性

廉价的Condition对象是不可变的;而且几乎没有计算开销。如果一个Condition对象在创建之后就是不可变的,那么框架安全管理器就可以立即简化以后的计算操作。也就是,如果不满足一个不可变的Condition对象,那么就可以不考虑上层元组,甚至可以不需要实例化一些Condition和Permission对象。

易变的Condition对象必须要在权限检查时进行计算。权限检查是非常常见的操作,因此需要对权限计算进行优化。应该避免不必要的权限检查。易变条件属于系统代码,必须要将它设计为在一种受限环境下工作。应该将方法isSatisfied()设计为是立即返回的。应该是根据变量来确定结果,而且要最小化副作用。

但是,有时候副作用是必需的;一个典型的例子就是用户提示。就如在权限检查算法[P220]一节中讨论的那样,对方法isSatisfied()的计算是延迟到检查的最后阶段进行。为了进行延迟,Condition对象必须要对方法isPostponed()返回true。

延迟计算的Condition对象必须要通过实现接口方法isSatisfied(Condition[],Dictionary)来对计算进行优化。在这个方法中,必须要计算一系列的条件式;这和指定的接口是不相关的。通过将计算分组进行可以最小化用户提示,最小化网络访问等。

下面的代码是检测一个操作是否由网络服务器进行授权。这是一个分组计算,在请求服务器校验之前,将所有的请求分组。其中的Host类代码是抽象的,在这里也没有展示。

   public class HostCondition implements Condition {

    String action;

   public HostCondition( Bundle, ConditionInfo info ) {

   action = info.getArgs()[0];

  }

   public boolean isSatisfied() { return false; }

     public boolean isPostponed() { return true; }

     public boolean isImmutable() { return false; }
  

     static Host host = new Host();

  
    public synchronized boolean isSatisfied(Condition[] conditions,

  Dictionary state ) {

   Set granted = (Set) state.get("granted");

   if ( granted == null ) {
 
  granted = new TreeSet();
  
     state.put("granted", granted );

   }

  Set pending = new TreeSet();

     for ( int i=0; i<conditions.length; i++ ) {

  String a = ((HostCondition)conditions[i]).action;

  if ( !granted.contains(a) )

  pending.add( a );

   }

     if ( pending.isEmpty() )

     return true;
  

    if ( ! host.permits( pending ) )

    return false;
    
     granted.addAll( pending );

    return true;
 }
  }

在Host Condition中有如下的Condition Info:

  [ HostCondition "payment" ]

在isSatisfied方法中包含了大部分代码,这个方法中有一个Condition对象数组的参数。而构造方法中只存储了操作(action)。

首先isSatisfied方法获得授予的权限集合。第一次调用这个方法的时候,这个集合并不存在,然后就创建并存储在state中,以后调用就可以直接使用这个state了。

然后,创建一个临时的集合pending,保存需要检查的条件的所有操作,并且减去所有在调用安全管理器(Security Manager)的checkPermission方法时,已经授予的操作权限。如果由于已经授予了所有的操作而导致pending列表为空,返回true。否则,查询服务器。如果服务器允许操作,那么将pending列表中的操作添加到state中的granted集合。

9.7.2.实现问题

9.7.2.1.在条件式中使用权限检查

如果在调用方法isSatisfied的代码中有几次进行权限检查,那么Condition的实现者应该使用AccessController doPrivileged来确保需要的权限。例如,由于用户提示条件式需要和用户通过界面进行交互,也就潜在的导致了很多权限检查。

但是,对于同一个Condition对象必须不能递归计算。框架必须要检测对条件式的递归计算,对第二次的调用返回一个不满足,而不是延迟计算Condition对象。

例如,如果计算一个用户提示条件式,而且需要访问UI,这样会导致对同样的用户提示Condition的计算,那么就不要进行第二次的计算,而是不对条件式延迟计算,直接返回false。

9.7.2.2.线程

Condition的实现需要保证对一个单独的checkPermission调用应该是发生在同一个线程中。但是,多权限的检查可以是在不同的线程中进行。Condition类的实现必须要确保这些同步问题可以得到解决。

9.7.2.3.类加载

所有的条件式都是来自启动类路径或者来自框架类加载器。这是由于安全原因同时也是由于实现包的多版本性。可以使用框架扩展bundle来下载带有bundle的条件式。

9.7.2.4.条件式生命周期

当框架重新启动或者是创建了bundle保护域(Bundle Protection Domain),则对Condition对象实例化。框架的实现可以通过优化来使得在一个bundle保护域的生命周期中多次创建并销毁Condition对象。Condition类的实现必须不能承担创建或者是间接引用了Condition类的职责。

查看评论