星期二, 三月 13, 2007

JBoss Seam 安全一点通

介绍
Seam框架内嵌了安全控制功能,能达到对业务方法和页面层面的控制。Seam安全机制是建立在JAAS的基础之上的,能够完美支持验证和授权。
Seam的安全机制在应用时分为两种模式:
--简单模式,支持验证服务和简单的基于角色的安全检测功能。
--高级模式,除了具备简单模式的功能外,还增加了使用JBoss Rules进行的基于规则的安全检测功能。
其中简单模式可以应用于大部分对安全没有特殊要求的场合,例如,需要通过角色来限定登陆用户访问的页面和执行的动作。
而高级模式主要应用于需要根据上下文状态或复杂的业务规则来进行安全检测的场合。
当使用高级模式的时候,Seam框架需要以下工具的支持(简单模式不需要):
drools-compiler-3.0.5.jar
drools-core-3.0.5.jar
commons-jci-core-1.0-406301.jar
commons-jci-janino-2.4.3.jar
commons-lang-2.1.jar
janino-2.4.3.jar
stringtemplate-2.3b6.jar
antlr-2.7.6.jar
antlr-3.0ea8.jar

为了使用基于Web的安全检测,jboss-seam-ui.jar必须被包含到WAR包中,并且要如下配置faces-config.xml:
《application》
《view-handler》org.jboss.seam.ui.facelet.SeamFaceletViewHandler《/view-handler》
《/application》

实现登陆验证
× 在components.xml中配置验证方法:
《components xmlns="http://jboss.com/products/seam/components"
xmlns:core="http://jboss.com/products/seam/core"
xmlns:security="http://jboss.com/products/seam/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=
"http://jboss.com/products/seam/core http://jboss.com/products/seam/core-1.2.xsd
http://jboss.com/products/seam/components http://jboss.com/products/seam/components-1.2.xsd
http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-1.2.xsd"
http://jboss.com/products/seam/security http://jboss.com/products/seam/security-1.2.xsd"》

《security:identity authenticate-method="#{authenticator.authenticate}"/》

《/components》
表达式#{authenticator.authenticate} 指定了authenticator验证器中的验证方法,当调用authenticator验证器的login时是就会执行验证方法。

× 实现验证逻辑
@Name("authenticator")
public class Authenticator {
@In EntityManager entityManager;

public boolean authenticate() {
try
{
User user = (User) entityManager.createQuery(
"from User where username = :username and password = :password")
.setParameter("username", Identity.instance().getUsername())
.setParameter("password", Identity.instance().getPassword())
.getSingleResult();

if (user.getRoles() != null)
{
for (UserRole mr : user.getRoles())
Identity.instance().addRole(mr.getName());
}

return true;
}
catch (NoResultException ex)
{
FacesMessages.instance().add("Invalid username/password");
return false;
}

}

}
注解@Name("authenticator")把本类注册为Seam框架中的验证器,Identity.instance().getUsername()用来获取用户名称,Identity.instance().getPassword()用来获取用户输入的密码,Identity.instance().addRole()用于为经过验证的用户添加角色。

× 实现登陆页面
《div》
《h:outputLabel for="name" value="Username"/》
《h:inputText id="name" value="#{identity.username}"/》
《/div》

《div》
《h:outputLabel for="password" value="Password"/》
《h:inputSecret id="password" value="#{identity.password}"/》
《/div》

《div》
《h:commandButton value="Login" action="#{identity.login}"/》
《/div》
identity.login()方法用于验证用户,如果需要退出系统则调用 #{identity.logout}。

× 异常处理
当登陆验证失败或者用户权限不足时系统会抛出异常,为了不向用户暴露错误信息需要对pages.xml做以下异常重定向配置:
《pages》

...

《exception class="org.jboss.seam.security.NotLoggedInException"》
《redirect view-id="/login.xhtml"》
《message》You must be logged in to perform this action《/message》
《/redirect》
《/exception》

《exception class="org.jboss.seam.security.AuthorizationException"》
《end-conversation/》
《redirect view-id="/security_error.xhtml"》
《message》You do not have the necessary security privileges to perform this action.《/message》
《/redirect》
《/exception》

《/pages》

× 登陆后重定向
考虑以下场景,当用户访问受保护的资源时,系统会跳转到登陆页面要求用户登陆,当用户通过验证后,需要返回到登陆页面以前的受保护页面。
配置pages.xml实现未登录用户访问受保护页面时跳转到登陆页面:
《pages login-view-id="/login.xhtml"》

《page view-id="/members/*" login-required="true"/》

...

《/pages》

配置components.xml实现登陆后跳转:
《event type="org.jboss.seam.notLoggedIn"》
《action expression="#{redirect.captureCurrentView}"/》
《/event》

《event type="org.jboss.seam.postAuthenticate"》
《action expression="#{redirect.returnToCapturedView}"/》
《/event》

使用容器JAAS配置
如果需要使用容器提供的JAAS控制而不是Seam内置的SeamLoginModule,那么需要在components.xml配置文件中指定jaasConfigName属性,例如:
jaas-config-name="other"/>

授权
Seam为实现对Components(组件)、component method(组件方法) 和 pages(页面)的访问控制内置了很多的安全特性。提示:如果要使用高级模式请按照上面的配置启用高级模式。

核心概念
Seam框架提供的各种授权机制都是建立于一个前提概念之上的,那就是“系统中的每一个用户都会被授予角色和(或者)权限”,正是通过判断用户所具有的角色和(或者)权限能否满足被使用资源的声明,授权才准确地得以实行。其中,角色代表了一个组或者是类别的概念,被授予角色的用户可能会拥有访问多个资源的多个权限,角色代表一个权限集。而权限是单一的,是作用于单个资源上的一个动作。虽然仅仅使用权限也可以实现授权,但角色的引入大大的方便了授权的实现和管理。在表示方法上,角色仅仅通过一个名词来表示,例如,”管理员“,”编辑“,”拟稿人“,”仓库管理员“等等,而权限需要一个名词动词对来表达,例如,“客户:新增”,“客户:删除”,“产品:查询”等等。

组件的安全控制
在Seam中,注解@Restrict被用于声明安全检测。
Seam可以提供在类或者方法级别的安全检测,如果同时声明了类和方法级别的安全检测,那么方法级别优先而类级别被忽略。如果在一个组件类上应用了@Restrict,相当于对类中的每个方法都应用@Restrict。如果方法执行时发生了安全检测失败,作为约定,Identity.checkRestriction()会抛出一个异常。

一个空@Restrict注解代表了对一个由组件类名称和方法名称组合而成的权限进行隐式检测,例如:
@Name("account")
public class AccountAction {
@Restrict public void delete() {
...
}
}
即是代表了对权限“account:delete”进行检测相当于@Restrict("#{s:hasPermission('account','delete',null)}")显式检测。
再看另一个例子:
@Restrict @Name("account")
public class AccountAction {
public void insert() {
...
}
@Restrict("#{s:hasRole('admin')}")
public void delete() {
...
}
}
组件”account“在类级别启用了隐式安全检测,因此如果”account“内部的方法没有覆盖@Restrict声明的话都必须执行隐式权限检测,比如方法”insert“需要执行相当于@Restrict("#{s:hasPermission('account','insert',null)}")的权限检测,而方法”delete“因为声明了方法级别的@Restrict所以它将执行显式的@Restrict("#{s:hasRole('admin')}")角色检测。
注意s:hasPermission()和s:hasRole()都是表达式函数,对它们的调用会委派到Identity的对应方法。这些表达式函数可以在任意的表达式中使用,非常方便。
因为是表达式,@Restrict注解可以引用Seam上下文中任意的对象,这在需要对特定的对象实例进行安全检测时是非常方便的,例如:
@Name("account")
public class AccountAction {
@In Account selectedAccount;
@Restrict("#{s:hasPermission('account','modify',selectedAccount)}")
public void modify() {
selectedAccount.modify();
}
}
在执行modify方法时会去Seam的上下文中查找”selectedAccount“对象,并传递其引用到Identity的hasPermission(),这样就可以判断用户是否拥有对”selectAccount“修改(modify)的权限。

代码安全检测
有时候需要在代码中直接执行权限的限制而不是使用@Restrict注解,这时候直接调用Identity.checkRestriction()方法对安全表达式进行评估即可,例如:
public void deleteCustomer() {
Identity.instance().checkRestriction("#{s:hasPermission('customer','delete',selectedCustomer)}");
}
如果安全表达式评估结果为假,那么会执行以下任意一种动作:
--如果用户没有登陆则抛出NotLoggedInException异常。
--如果用户已经登陆则抛出AuthorizationException异常。
同理,也可以在代码中直接使用Identity.hasPermission()和Identity.hasRole()方法:
if (!Identity.instance().hasRole("admin"))
throw new AuthorizationException("Must be admin to perform this action");

if (!Identity.instance().hasPermission("customer", "create", null))
throw new AuthorizationException("You may not create new customers");

参考文档

3 条评论:

匿名 说...

我也在学习Seam,想跟你学习交流一下,我QQ 61304189

匿名 说...

我是学习Seam的,大家有机会加下我,共同讨论下.

匿名 说...

我是学习Seam的,大家有机会加下我,共同讨论下.QQ:329139017