第15章 安全(2)

15 安全(2)


15.5. 错误消息

 

安全API对各种安全相关事件产生了许多缺省面消息。下面的表列出了消息关键字, message.properties能通过在一个message.properties资源文件中指定它们覆盖这些消息。禁用这些消息,只在资源文件中设置关键字为空就行了。

 

15.6. 安全消息关键字

消息关键字

描述

org.jboss.seam.loginSuccessful

当一个用户通过安全API成功登录时,这个消息会产生。

org.jboss.seam.loginFailed

当登录过程失败时,因为用户提供了错误的用户名或密码,或者因为其他一些方式认证故障,这个消息会产生。

org.jboss.seam.NotLoggedIn

当一个用户企图执行一个动作或访问需要一个安全检查的页面时,并且用户目前无法认证,这个消息会产生。

org.jboss.seam.AlreadyLoggedIn

当一个已经被认证的用户企图再次登录时,这个消息会产生。

 

15.6. 授权

 

对安全访问组件、组件方法和页面,Seam安全API提供了许多授予机制。本节描述所有这些。注意一个重要的事情,如果你希望使用任何一个高级功能(如基于角色的许可),那么你的components.xml可能需要配置来支持这种功能——看上面的配置章节。

 

15.6.1. 核心概念

 

Seam安全是建立在被授予角色和/或权限的用户的前题下,允许他们执行没有必要的安全特权的其他用户不得准许的操作。Seam提供的任何授权机制是建立在角色和许可这个核心概念上的,用一个可扩展的框架提供了多种方法保护应用程序资源。

 

15.6.1.1. 角色是什么?

 

一个角色是一个用户组或用户类,在一个应用程序内,他们已经被授予某些执行一个或多个指定动作的特权。他们被简单地构造,只由一个名字组成,如"admin", "user", "customer"等等。他们也能被授予给用户(或者在一些情况下给另外的角色),并且常常创建逻辑用户组为方便特殊的应用程序特权分配。

 

 

15.6.1.2. 许可是什么?

 

一个许可是一个特权(有时候是一次性的)为处理单一的特殊动作。构建一个只使用许可的应用程序是完全可能的,然而当授予特权给用户组时,角色能提供更高水平的方便。许可在结构上比角色稍微复杂,在本质上由三个“方面”构成,一个对象,一个动作和一个接收器。一个许可的对象是一个特殊动作被允许由一个特殊接收器(或用户)执行的对象。例如,用户"Bob"可以有许可删除customer对象。在这个案例里,许可对象是“customer”,许可动作应该是“delete”,接收者应该是“Bob”。

 

 

在这个文档内部,许可通常是用形式target:action表示(忽略接收器,尽管在现实中是必要的)。

 

15.6.2. 保护组件

 

让我们先查看最简单的授权形式,组件安全,开始使用@Restrict注释。

 

提示:@Restrict  vs  typesafe安全注释

虽然使用有支持EL表达式能力的@Restrict注释为安全组件方法提供了一个强大而灵活的方法,但推荐使用等价的typesafe(稍后描述),至少它提供了编译时间安全。

 

15.6.2.1. @Restrict 注释

 

使用@Restrict注释,Seam组件也可以在方法或类级别被保护。如果方法和它的声明类都用@Restrict注释, 方法约束会优先(并且类约束不会应用)。如果方法调用忽略了安全检查,那么异常会按照Identity.checkRestriction()(看内联约束)的合同被抛出。 一个@Restrict 只是在组件类本身是等价于增加@Restrict到它的所有方法。

 

一个空的@Restric隐含着componentName:methodName许可检查。例如下面的组件方法:

 

@Name("account")

public class AccountAction {

    @Restrict public void delete() {

      ...

    }

}

 

在这个例子,要求调用delete()方法的隐含许可是account:delete。这等价于编写@Restrict("#{s:hasPermission('account','delete')}")。现在让我们看另外一个例子:

 

@Restrict @Name("account")

public class AccountAction {

    public void insert() {

      ...

    }

    @Restrict("#{s:hasRole('admin')}")

    public void delete() {

      ...

    }

}

 

这次,组件类自身被@Restrict注释。这意味着任何方法不用覆盖@Restrict注释要求隐含许可检查。这个例子的这种情况下,insert()方法要求account:insert许可,同时delete()要求用户是一个admin角色成员。

 

在我们再进一步之前,让我们关注一下在上面例子中看见的#{s:hasRole()}s:hasRole  s:hasPermission都是EL函数,它们委托给Identity类相应的命名方法。这些函数能贯穿整个安全API使用在任何EL表达式的内部。

 

作为EL表达式,@Restrict注释的值可以引用存在于Seam上下文内部的所有对象。当对一个特殊对象实例进行许可检查时,这尤其有用。看一下这个例子:

 

@Name("account")

public class AccountAction {

    @In Account selectedAccount;

    @Restrict("#{s:hasPermission(selectedAccount,'modify')}")

    public void modify() {

        selectedAccount.modify();

    }

}

 

从这个例子中注意到有趣的东西是引用的selectedAccount,看hasPermission()函数调用的内部。这个变量的值会从Seam上下文中找到,并且用Identity传递给hasPermission()方法,如果用户有修改指定的Account对象必需的许可,那么在这种情况下能够确定。

 

15.6.2.2. 内联约束

 

有时候不使用@Restrict注释而用代码执行安全检查是令人想要的。在这种情形下,简单地使用Identity.checkRestriction()求得一个安全表达式的值,象这样:

 

public void deleteCustomer() {

    Identity.instance().checkRestriction("#{s:hasPermission(selectedCustomer,'delete')}");

}

 

如果指定的表达式求得的不是true值,

*如果用户没有注册, NotLoggedInException异常抛出,或

*如果用户注册了, AuthorizationException 异常被抛出。

 

Java代码直接调用hasRole() hasPermission()方法也是可能的:

 

if (!Identity.instance().hasRole("admin"))

     throw new AuthorizationException("Must be admin to perform this action");

if (!Identity.instance().hasPermission("customer", "create"))

     throw new AuthorizationException("You may not create new customers");

 

15.6.3.用户界面安全

 

一个精心设计的用户界面,其标志是用户对他们不具备必要的特权使用的界面不授予选项。Seam安全允许条件渲染1)页面的片断或2)独立的控件,根据用户权限,使用与组件安全同样的EL表达式。

 

让我们看一些界面安全的例子。首先,让我们假装我们有一个注册表单,如果用户尚未登录,它会被渲染。使用identity.isLoggedIn()属性,我们能这样编写:

 

<h:form class="loginForm" rendered="#{not identity.loggedIn}">

 

如果用户没有注册,那么注册表单将被渲染——非常直接。 现在我假装在这个页面上有一个菜单,包含一些动作,用户仅能以管理员角色可以访问。这里是一个编写这些的方法:

 

<h:outputLink action="#{reports.listManagerReports}" rendered="#{s:hasRole('manager')}">

    Manager Reports

</h:outputLink>

 

这也是相当直接的。如果用户不是一个管理员角色成员,那么这个超链接不会被泻染。通常泻染属性能用在控件自身,或者在<s:div> <s:span>围绕的控件。

 

现在看一些更复杂的情况。比方说,你在页面上有一个h:dataTable控件,列出你可能希望或不希望依赖于用户特权渲染动作的链接记录清单。s:hasPermission EL函数允许我们传递一个对象参数,用来确定是否用户对那个对象有请求的许可。这里是一个带有保护链接的dataTable

 

<h:dataTable value="#{clients}" var="cl">

    <h:column>

        <f:facet name="header">Name</f:facet>

        #{cl.name}

    </h:column>

    <h:column>

        <f:facet name="header">City</f:facet>

        #{cl.city}

    </h:column>

    <h:column>

        <f:facet name="header">Action</f:facet>

        <s:link value="Modify Client" action="#{clientAction.modify}"

                rendered="#{s:hasPermission(cl,'modify')"/>

        <s:link value="Delete Client" action="#{clientAction.delete}"

                rendered="#{s:hasPermission(cl,'delete')"/>

    </h:column>

</h:dataTable>

 

15.6.4. 保护页面

 

页面安全要求应用程序使用pages.xml文件,然而配置特别简单。只是包含<restrict/>元素在你希望保护的页面元素内部就行了。如果没有用restrict元素指定明确的约束,当页面通过non-facesGET)请求访问时,隐含的许可viewId.xhtml:render会被检查,以及当来自页面的任何JSF回滚(表单提交)引发时,viewId.xhtml:restore许可会被要求。另外,指定的约束作为标准的安全表达式会被求值,这里是几个例子:

 

<page view-id="/settings.xhtml">

    <restrict/>

</page>

 

这个页面有一个隐含的/settings.xhtml:render许可需求为non-faces请求,以及一个隐含的/settings.xhtml:restore许可为faces请求。

 

<page view-id="/reports.xhtml">

    <restrict>#{s:hasRole('admin')}</restrict>

</page>

 

对这个页面的facesnon-faces请求需要用户是一个admin角色成员。

 

15.6.5. 保护实体

 

Seam安全也使对实体的read, insert, update delete动作应用安全约束成为可能。

 

为保护一个实体类的所有动作,增加一个@Restrict注释在类自身上就可以了:

 

@Entity

@Name("customer")

@Restrict

public class Customer {

  ...

}

 

如果在@Restrict注释内没有指定表达式,默认的安全检查是执行entityName:action许可检查,entityNameSeam实体组件名(或者合格类的名字,如果@Name没有被指定),并且动作是read, insert, update delete中的任何一个。

 

仅仅约束某些动作也是可能的,通过@Restrict注释在相应的实体生命周期的方法上(如下面的注释):

 

* @PostLoad ——在一个实体实例从数据库加载后调用。使用这个方法配置read许可。

* @PrePersist ——在一个实体的新实例被插入前调用。使用这个方法配置insert许可。

* @PreUpdate ——在一个实体被更新前调用。使用这个方法配置update许可。

* @PreRemove —— 在一个实体被删除前调用。使用这个方法配置delete许可。

 

这里是一个实体如何被配置来对所用insert操作执行安全检查的一个例子。请注意,方法不需要做任何事,唯一重要事情是关于安全如何被注释:

 

  @PrePersist @Restrict

  public void prePersist() {}

  

 

提示:使用 /META-INF/orm.xml

 

你也能用/META-INF/orm.xml指定回调方法:

 

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"

                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm

 http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"

                 version="1.0">

   <entity class="Customer">

      <pre-persist method-name="prePersist" />

   </entity>

</entity-mappings>

 

当然,你仍然需要在Customer中用@Restrict注释prePersist()方法

 

 

这里是一个实体许可规则的例子,检查认证用户是否被允许插入一个新的MemberBlog记录(来自seamspace例子)。制成的安全检查的实体被自动插入工作内存(在这个MemberBlog案例中):

 

rule InsertMemberBlog

  no-loop

  activation-group "permissions"

when

  principal: Principal()

    memberBlog:  MemberBlog(member  :  member  ->

 (member.getUsername().equals(principal.getName())))

  check: PermissionCheck(target == memberBlog, action == "insert", granted == false)

then

  check.grant();

end;

 

 

如果当前认证用户(被Principal实事标明的)与被创建的blog条目成员有同样的名字,这个规则会授予memberBlog:insert许可。在例子代码中看见的"principal: Principal()"结构是一个变量捆绑——它从工作内存捆绑了Principal对象实例(认证期间放在那里),并且分配它给principal变量。变量捆绑允许值在其它地方被引用,如紧随的那行,比较成员用户名与Principal名。为更详细的内容,请参考JBoss Rule文档。

 

最后,我们需要安装一个侦听器类,将Seam安全与你的JPA提供者集成。

 

15.6.5.1. JPA 的实体安全

 

处理EJB3实体baen的安全检查,用了一个EntityListener。你能通过使用下面的META-INF/orm.xml文件安装侦听器:

 

<?xml version="1.0" encoding="UTF-8"?>

<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"

                 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

                 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/

xml/ns/persistence/orm_1_0.xsd"

                 version="1.0">

    <persistence-unit-metadata>

        <persistence-unit-defaults>

            <entity-listeners>

                <entity-listener class="org.jboss.seam.security.EntitySecurityListener"/>

            </entity-listeners>

        </persistence-unit-defaults>

    </persistence-unit-metadata>

</entity-mappings>

 

15.6.5.2. 用一个受管理的Hibernate会话的实体安全

 

如果你通过Seam使用一个配置的Hibernate SessionFactory,并且使用注释,或orm.xml,那么你不需要为使用实体安全做任何特殊事情。

 

15.6.6. Typesafe 许可注释

 

Seam提供一些注释,可用来作为@Restrict的替代选择, 它有提供编译时间安全的附加优势,它们不支持专门的EL表达式,同样完成@Restrict所做事

 

开箱即用,Seam附带有标准的(CRUD-based)基于弄脏的许可注释,然而,增加你自己的是一个简单的事情。下面注释在org.jboss.seam.annotations.security包中提供:

 

* @Insert

* @Read

* @Update

* @Delete

 

要使用这些注释,简单地放置它们在你希望执行安全检查的方法或参数上就行了。如果放置在一个方法上,那么应该指定一个许可会被检查的目标类,举例如下:

 

  @Insert(Customer.class)

  public void createCustomer() {

    ...

  }

 

在这个例子里,对用户的一个许可检查会被执行,确保他们有权力创建新的Customer对象。许可检查的对象是Customer.class(实际的java.lang.Class实例本身),并且动作是小写字母代表的注释名,在这个例子里是insert

 

同样地注释一个组件方法的参数也是可能的。如果这样做,那么不需要指定许可目标(作为参数值本身会是许可检查的对象):

  public void updateCustomer(@Update Customer customer) {

    ...

  }

 

创建你自己的安全注释,你只需用@PermissionCheck 注释它,例如:

@Target({METHOD, PARAMETER})

@Documented

@Retention(RUNTIME)

@Inherited

@PermissionCheck

public @interface Promote {

   Class value() default void.class;

}

 

annotation name) with another value, you can specify it within the @PermissionCheck annotation:

如果你希望用另外的值覆盖缺省的许可动作名(小写字母版注释名),你能指定它在@PermissionCheck注释内:

 

@PermissionCheck("upgrade")

 

 

 

15.6.7. Typesafe 角色注释

 

除去支持typesafe许可注释之外,Seam安全也支持typesafe角色注释

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值