@Restrict注解
Seam组件可以通过使用@Restrict
来实现方法级别或者类级别的安全控制。如果一个方法和这个方法所在类同时使用了@Restrict
注解,那么方法级别的安全限制优先级更高(意味着类级别的限制不起作用)。如果在方法上验证失败,那么与执行Identity.checkRestriction()方法一样抛出一个异常(参考相关的文档)。把
@Restrict加载一个组件类上,相当于将这个约束加在了这个类的所有方法上。
一个空的@Restrict约束默认使用
componentName:methodName作为约束。例如以下这段代码:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
@Name(
"
account
"
)
public
class
AccountAction { @Restrict
public
void
delete() { ... } }
在这个例子中,默认调用delete()方法需要
account:delete权限。效果与
@Restrict("#{s:hasPermission('account','delete')}")相等。
再来看下面这段代码:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
@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类里面的方法。
这个方法能够用在所有EL表达式中,并且会用在所有安全验证相关的部分。
作为一个EL表达式,@Restrict注解的值可以关联到任何一个Seam上下文中存在的对象上。这在对某个对象实例进行权限验证的时候非常有用。
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
@Name(
"
account
"
)
public
class
AccountAction { @In Account selectedAccount; @Restrict(
"
#{s:hasPermission(selectedAccount,'modify')}
"
)
public
void
modify() { selectedAccount.modify(); } }
上面这个例子中,有一段代码比较有趣。就是在hasPermission()方法内部使用到了selectedAccount对象。此时,Seam会到上下文中寻找这个对象实例,并且将它传递给Identity对象的hasPermission()方法。这样就能判断出当前用户是否有权限来修改Account对象。
15.6.2.2. 内部约束(Inline restrictions)
有的时候你可能会需要在某个方法的代码内部检查权限,这样就无法使用@Restrict
注解。这种情况下,你就需要用到Identity.checkRestriction()方法来执行一个与安全有关的表达式,例如:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
public
void
deleteCustomer() { Identity.instance().checkRestriction(
"
#{s:hasPermission(selectedCustomer,'delete')}
"
); }
如果表达式未返回true则:用户没有登录——抛出NotLoggedInException异常,用户已经登录——抛出AuthorizationException异常。
同样的,你也可以在任何Java代码中使用hasRole()和hasPersmission()方法。
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
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. 程序界面中的安全控制(Security in the user interface)
一个良好的用户界面设计需要符合一个特征,用户应该只看见他所能操作的部分,而用户没有权限看见或者没有权限进行操作的按钮链接等,不应该显示出来。Seam提供了两种根据权限控制界面显示内容的方法:1)根据权限判断页面是否显示,2)根据权限判断界面中的某个控件是否显示。这两种控制都使用EL表达式来判断。
我们来看几个权限控制的例子。
首先,一个登录表单需要只有当用户没有登录的时候才能看见。如果用户已经登录,那么就不需要显示登录表单。这个功能可以使用identity.isLoggedIn()属性来判断,在页面上,我们可以写成这样:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
<
h:form
class
="loginForm"
rendered
="#{not identity.loggedIn}"
>
这种控制方式非常直观,看见代码的字面意思就应该能明白。下面,我们假设页面上有个显示所有报表的链接,这个链接只有当用户拥有manager角色的时候才能看见。我们在页面上可以这么写:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
<
h:outputLink
action
="#{reports.listManagerReports}"
rendered
="#{s:hasRole('manager')}"
>
Manager Reports
h:outputLink>
这段代码也很直观。如果用户没有manager角色,那么这个链接就不会显示。通常rendered属性可以直接卸载具体的控件内部,但是根据实际情况,也可以将它写在某个父标签上,例如
或。
现在,我们来看一个复杂点的例子。假设页面上有一个h:dataTable,用于显示一个记录列表。表格的最后一列用来显示对当前记录的操作链接,这个链接需要根据用户拥有的权限来判断是否显示。s:hasPermission表达式允许我们将当前行的记录作为参数传入权限判断函数,并根据传入的对象以及当前登录的用户来判断是否显示。下面就是这个表格的代码:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
<
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">Cityf:facet> #{cl.city} h:column> <h:column> <f:facet name="header">Actionf: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. 页面安全控制(Securing pages)
页面安全控制需要程序里面有pages.xml文件,不过配置代码非常简单。只需要在page标签内部添加一个对象就能够实现安全控制。
如果restrict对象没有任何属性或者子对象,那么默认情况下,来自non-faces (GET)的请求会需要权限
/viewId.xhtml:render,
JSF postback(来自表单提交方式)的请求会需要权限/viewId.xhtml:restore
。此外,如果想要指定约束规则,那么只要写出标准的安全验证表达式即可。下面是几个例子:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
<
page
view-id
="/settings.xhtml"
>
<
restrict
/>
page> <page view-id="/reports.xhtml"> <restrict>#{s:hasRole('admin')}restrict> page>
15.6.5. 实体安全控制(Securing Entities)
Seam安全模块同样允许将实体的添加、修改、删除操作用安全规则来控制。如果想要将对实体的所有操作用Seam安全模块来控制,那么只要在实体类上添加一个@Restrict
注解:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
@Entity @Name(
"
customer
"
) @Restrict
public
class
Customer { ... }
如果@Restrict注解内部没有写任何权限判断表达式,那么默认的权限为
entityName:action,这里的
entityName为Seam组件名称(如果没有使用@Name注解来指定实体的Seam组件名称,那么也可以使用fully-qualified类名称来代替)。冒号后的操作有四种,分别是:read、insert、update、delete。
也可以单独指定某个生命周期操作,在@Restrict注解之外添加相应的注解到对应生命周期的方法上即可。共有以下四种注解:
下面这个例子介绍了如果给所有insert操作添加安全检查。请注意,这个方法并不需要做任何事情,唯一需要注意的就是,这个方法是如何被声明的:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
@PrePersist @Restrict
public
void
prePersist() {}
你也可以在/META-INF/orm.xml
文件中定义:
<!-- Code highlighting produced by Actipro CodeHighlighter (freeware) http://www.CodeHighlighter.com/ -->
<!--sp-->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>
当然,你仍然需要使用@Restrict
来注解Customer对象中的prePersist()方法。
下面这个例子介绍了使用规则判断当前用户是否有权限添加一篇新的MemberBlog记录(来自seamspace示例)。The entity for which the security check is being made is automatically inserted into the working memory (in this case MemberBlog
):