`

Seam 2.1中的安全升级 (二)

    博客分类:
  • Seam
阅读更多
Permission Management

尽管 Identity Management 提供了一致的API来管理用户帐户,我们依然需要一个方式来管理用户许可(Permission)。从前一个版本以来,在Seam 2.1.0中的验证特性经过了整个的修订。在以前开发者需要继承一个内建的 Identity 组件来实现自定义的许可(Permission)检查,在Seam 2.1.0中提供了一个可插拔的系统,可以让你注册你自己的许可(Permission)分析器而不用继承其他的组件了。下图展示了这些东西是如何协作的:






在上图中, Identity 现在使用 PermissionMapper 映射一个特别的ResolverChain来执行许可(Permission)检查,该ResolverChain可以配置一个或多个PermissionResolvers。Seam提供了 RuleBasedPermissionResolver (for resolving rule-based permission checks) 和 PersistentPermissionResolver (for performing checks based on permissions stored in persistent storage, such as a database)。如果你的程序需要自定义安全管理,实现你自己的PermissionResolver 也是相当简单的。

我想在进一步前进之前,我们需要定义下许可(Permission)是什么。在Seam中,许可(Permission)有3个方面:

    * A target, 一个在某些方面起作用的对象
    * An action, 在 target 上被执行的动作
    * A recipient, 具有在target上执行特定action的许可(Permission)的用户或则角色实体
   
   


   
许可(Permission)检查的target就是PermissionMapper用来判断使用那个ResolverChain来执行检查。这样可以实现为不同的对象配置不同的PermissionResolvers。例如:你可能希望仅仅使用RuleBasedPermissionResolver来执行Customer对象的许可(Permission)检查,同时使用PersistentPermissionResolver来执行Invoice对象上的许可(Permission)检查。PermissionMapper支持这种灵活性。
让我们实际的看看这些应用。


Persistent Permissions

SeamSpace示例允许用户上传一个图片。其他用户在浏览用户信息的时候可以看到该图片:







现在我们假设一些图片你想设置为私有的,而其他一些图片你想仅仅显示给你的朋友。点击你图片下的 padlock 图标,会打开图片的许可(Permission)管理页面:




在这个页面,我们可以看到那些用户和角色有查看这个图片的许可。在这个示例中,仅仅我的朋友具有查看该图片的权限。现在我想让该站点的任何用户都可以看到该图片。可以点击'new permission'来授权,会打开许可详细信息页面:





在这里我们通过选择角色或则具体的用户来提供特殊的许可权限。我希望所有的用户都可以查看,所以我从角色列表中选择‘user’,然后点击‘view’ checkbox。点击save按钮保存设置,然后返回到许可管理界面,在这里可以看到我设置的新许可:





当我们授予一个新的许可的时候,实际发生了什么呢?我们看看在这个场景后面使用的组件,ImagePermission. 下面是相关的代码:

@Name("imagePermission")
@Scope(CONVERSATION)
public class ImagePermission implements Serializable
{
   // (snip)
   @In PermissionManager permissionManager;
   @In PermissionSearch permissionSearch;

   private MemberImage target;  
   private Principal recipient;

   @SuppressWarnings("unchecked")
   @Begin(nested = true)
   public void createPermission() {
      target = (MemberImage) permissionSearch.getTarget();    
      // (snip)
   }

   public void applyPermissions() {
      // (snip)
  
      List<Permission> permissions = new ArrayList<Permission>();
    
      for (String role : selectedRoles)
      {
         Principal r = new Role(role);
         for (String action : selectedActions)
         {          
            permissions.add(new Permission(target, action, r));
         }
      }
     
      for (Member friend : selectedFriends)
      {
         MemberAccount acct = (MemberAccount) entityManager.createQuery(
               "select a from MemberAccount a where a.member = :member")
               .setParameter("member", friend)
               .getSingleResult();
       
         Principal p = new SimplePrincipal(acct.getUsername());
       
         for (String action : selectedActions)
         {
            permissions.add(new Permission(target, action, p));
         }
      }
     
      permissionManager.grantPermissions(permissions);

      Conversation.instance().endBeforeRedirect();
   }

   // (snip)
}

从上面的代码中我们可以看到 ImagePermission 是一个会话作用域(conversation-scoped)的组件。其实,它的功能被实现为一个嵌套的作用域,在同一个target对象上可以同时打开多个 'new permission' 窗口来操作。我们还可以看到PermissionManager通过@In 注解注入进来了。


现在 createPermission() 方法开始一个嵌套的会话 (thanks to the @Begin(nested = true) annotation),然后在我们的target对象中保存一个引用。当用户点击save按钮,在分配了指定的许可后,用来构建一些Permission对象来执行授权的applyPermissions()方法被调用了。在该函数内,使用一些授权的许可来调用PermissionManager.grant()。我们发些时间来详细解释下这个 PermissionManager (the heart of the Permission Management API)。


The PermissionManager Component

就像 IdentityManager 用来处理用户和角色操作一样,PermissionManager 被设计为来操作许可。它提供了API来允许授权和激活许可,或则一个target对象的列表。我们来看看一些方法:

    * listPermissions(String target, String action)
    * listPermissions(Object target)
    * grantPermission(Permission permission)
    * grantPermissions(List<Permission> permissions)
    * revokePermission(Permission permission)
    * revokePermissions(List<Permission> permissions)

就像 IdentityManager 需要一个 IdentityStore 来保存数据, PermissionManager 也需要一个 PermissionStore 来和持久化存储打交到。Seam 只提供了一个 PermissionStore 实现 - JpaPermissionStore, 可以通过JPA来和数据库存在互操作。在理论上也可以把许可信息保存在LDAP目录中,或则一个普通文件中,但是大多数情况下还是保存在一个数据库中的。

现在来看看SeamSapce示例中 AccountPermission entity bean的代码,下面的代码为了排版被截断了:


@Entity
public class AccountPermission implements Serializable
{
   // snip field declarations, etc
    
   @PermissionUser @PermissionRole
   public String getRecipient() { return recipient; }
   public void setRecipient(String recipient) { this.recipient = recipient; }

   @PermissionTarget public String getTarget() { return target; }
   public void setTarget(String target) { this.target = target; }

   @PermissionAction
   public String getAction() { return action; }
   public void setAction(String action) { this.action = action; }

   @PermissionDiscriminator
   public String getDiscriminator() { return discriminator; }
   public void setDiscriminator(String discriminator) { this.discriminator = discriminator; }
}

再一次的,我们注意到使用了一些特殊的注解。这里我告诉你,许可既可以指定到用户上也可以指定到角色上。这就意味着可以把许可信息保存的分开的表格中,这从性能角度来说,把他们保存在单个表中,使用一个辨别器列(discriminator column)来区分他们,可能看起来更合适。因此在上面的代码中,我们看到getDiscriminator() 使用了@PermissionDiscriminator注解,表明该列用来判断许可是应用到用户上还是角色上。

继续向下看,用来配置一个保存许可信息的实体注解如下:

    * @PermissionUser - designates the field that contains the name of the recipient of the permission (for user-assigned permissions)
    * @PermissionRole - same as above, but for role-assigned permissions
    * @PermissionTarget - contains a unique identifier string, identifying a single instance of an object. Alternatively, can contain a class name or any arbitrary string for the designation of more generalised permissions.
    * @PermissionAction - contains a list of the actions that the recipient may perform on the target object.
    * @PermissionDiscriminator - see paragraph above


这里说明下 - permission management 特性只用来管理持久的许可信息( persistent permissions)。当然在大多数情况下,你可能希望通过业务逻辑来应用许可权限,例如:用户应该具有查看和管理他们自己图片的权限。这类许可有Seam的rule-based security(基于角色的安全)来处理,在下面我们来详细讨论下这个问题。


Rule-based Permissions


Seam 基于Drools 提供了一个rule-based security模型,因此这并不是一个真正的新功能。我们这里来重新温习下这是如何应用到我们的示例项目SeamSpace中的。

继续图片安全这个话题,当处理用户图片的时候,除了我们上面讨论的问题外,显然我们需要一些基础的安全规则。在默认的情况下,查看用户的图片被安全规则限制了,这意味着在SeamSpace中,如果你想看一个用户的图片,你必须在被该用户授权后才可以查看(either via a persistent permission grant, or a security rule)。另外请记住,我们特别的需要允许下面的一些事情:

    * Users should be allowed to grant and revoke permissions for their own images
    * Users should be allowed to delete their own images
    * User profile images (a user's main image, i.e. their 'avatar') should always be viewable by anyone
    * Users should always be allowed to view their own images (of course)
    * User images with 'friend' permissions should be viewable by the user's friends (more on this in a bit)

现在我们详细的看看这些规则。首先,用户应该允许给他们的图片授予和撤回许可权限。这是相当直接的,并且作为两个独立的规则实现了。当和对象许可一起使用的时候,Seam会插入一个PermissionCheck对象到Drools的工作内存(working memory)中,该内存包含许可目标和根据用户要做什么分别由'seam.grant-permission' 或则 'seam.revoke-permission'(based on whether PermissionManager.grantPermission() or PermissionManager.revokePermission() is called)定义的一个动作(containing both the target of the permission, and an action either being 'seam.grant-permission' or 'seam.revoke-permission' depending on what the user is trying to do (based on whether PermissionManager.grantPermission() or PermissionManager.revokePermission() is called))。用于验证用户的MemberAccount示例一直在working memory中,在实际上下面的规则是: '如果我们正在处理的MemberImage是属于当前登陆的用户的,那么就授予许可权限(if the MemberImage for which we're performing the permission check is owned by the current user, then grant the permission)':

rule GrantImagePermissions
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    image: MemberImage(mbr : member -> (mbr.memberId.equals(acct.member.memberId)))
    check: PermissionCheck(target == image, action == "seam.grant-permission", granted == false)
then
    check.grant();
end

rule RevokeImagePermissions
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    image: MemberImage(mbr : member -> (mbr.memberId.equals(acct.member.memberId)))
    check: PermissionCheck(target == image, action == "seam.revoke-permission", granted == false)
then
    check.grant();
end

在下一步,我们也需要一个允许用户删除他们自己图片的规则。和第一个规则相似,我们验证当前执行删除的图片是否属性该用户的,如果是那么就授予权限:

rule DeleteImage
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    image: MemberImage(mbr : member -> (mbr.memberId.equals(acct.member.memberId)))
    check: PermissionCheck(target == image, action == "delete", granted == false)
then
    check.grant();
end

用于查看用户图片的规则有点不同。这里我们简单的测试 -- 正在查看的图片是用户自己的图片(Here we simply test that the image being viewed is the profile image for the owning member) (i.e. image.getMember().getPicture() == image):

rule ViewProfileImage
    no-loop
    activation-group "permissions"
when
    image: MemberImage()
    check: PermissionCheck(target == image, action == "view", granted == false)
    eval( image.getMember().getPicture() == image )
then
    check.grant();
end

还有,用户应该总是可以查看他们自己的图片。这个权限验证类似于第一个规则,在那里我们简单的验证图片的所有者是否为当前登陆的用户:

rule ViewMyImages
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    image: MemberImage(mbr : member -> (mbr.memberId.equals(acct.member.memberId)))
    check: PermissionCheck(target == image, action == "view")
then
    check.grant();
end
Conditional Roles

最后,为了当我们的朋友查看我们的图片,我们需要定义一个特殊的规则。在前面我们看到了如何给‘friends’角色授权,然而根据上下文的不同‘riend’可能具有不同的意思。可以认为,在系统中的任何一个用户都是某人的‘friend’,你们当其他人来看图片的时候我们如何定义他们是否是一个‘friend’呢?这就是条件角色(conditional roles )的领域了,这些角色是特殊的,并且不能直接的授予给用户。

当一个对象的权限检查通过安全(security)API检查过后, permission manager就通知 security API一个conditional role被授予权限了,需要执行一个特殊的基础规则(rule-based)来验证该用户是否具有该角色,但在仅仅在permission check的上下文中执行。要实现这个功能,和往常一样把一个PermissionCheck对象插入到包含target和action的working memory中,然而也会附加的插入一个RoleCheck对象,该对象包含用来对比的conditional role的名字。这样我们可以写一个安全规则来验证是否授予conditional role权限:

rule FriendViewImage
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    image: MemberImage(mbr : member -> (mbr.isFriend(acct.member)))
    PermissionCheck(target == image, action == "view")
    role: RoleCheck(name == "friends")
then
    role.grant();
end

这个规则检查当前验证的用户是否在图片所有者的朋友列表中。如果在的话,角色就暂时的授予这个许可检查。在某些复杂安全规则的情况下这具有非常大的灵活性( This allows great flexibility in assigning complex security rules to dynamic groups of users (in this case, a user's friends list) that don't necessarily warrant having their own role/group, due to either impracticality or design restrictions).


Strongly-typed Security Annotations

最后,作为结束我们来看看一些新的安全注解。为了使Seam Security看起来更'Web Beansy',我们引入了一些用于现在组件方法的类型安全的注解。通过使用meta-annotations,我们可以提供一些安全注解来应用安全限制到函数上或则参数上。Seam提供了一些开箱即用的注解,他们是标准的CRUD注解 (@Insert, @Read, @Update, @Delete) ,并且添加你自己的注解也是非常简单的。看看下面的例子:

@Begin @Insert(Customer.class)
public void createCustomer() {

这个注解的功能是,除非你具有插入新customer对象的权利,放置你不能调用createCustomer方法。类似的,我们可以注解一个方法的参数:

public void updateCustomer(@Update Customer customer) {

创建自己的注解只需要使用@PermissionCheck就可以了。来看个示例,你希望创建一个新的‘Promote’许可。该注解可以非常简单的实现,如下:

@Target({METHOD, PARAMETER})
@Documented
@Retention(RUNTIME)
@Inherited
@PermissionCheck
public @interface Promote {
     Class value() default void.class;
}

定义后,就可以直接使用了:

public void promoteStaff(@Promote Staff person) {

如果写一个基于规则的许可,该规则看起来就像这样:

rule PromoteStaffMember
    no-loop
    activation-group "permissions"
when
    acct: MemberAccount()
    Role(name == 'admin')
    staff: Staff()
    check: PermissionCheck(target == staff, action == "promote")
then
    check.grant();
end

许可动作(permission action)变为注解的小些名称了。这真的是很简单的!

同时我们仍然支持遗留的用于基于表达式的安全检查的@Restrict注解,我建议各位都使用新的类型安全的注解,至少在编译期安全检查就提供了简单的验证。


Conclusion

到这里就结束了JBoss Seam中的新安全特性。这里是一些参考的链接:

JBoss Seam Community Site (downloads, documentation, forums) - http://www.seamframework.org/

JBoss Seam 中文社区站点(下载,文档,论坛) - http://www.seamframework.cn/

JBoss Home Page - http://www.jboss.org
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics