返回目录列表
用户登陆授权模块
用户Model
主要有下列字段:
private String userId; //主键
private String username; //登陆用户名
private String password; //登陆密码
private String email; //登陆Email
这四个字段是用户登陆用主要字段,如果你有一个统一认证服务器,那么这四个字段不但需要在本系统数据库持久化,还需要保存在统一认证服务器中。
在我们统一认证服务器中,我们已经一个com.jdon.security.UserModel(存在Jdon框架中),因此,这里Account只要继承这个UserModel,加上一些附加字段。
与统一认证服务器关系处理方案如下:
用户新注册和资料修改在本系统提供,当保存到数据库中时,进行两个数据库同时保存,将用户相关验证信息保存到统一认证服务器中;将其他用户相关信息保存在本系统数据库中。
以下授权验证配置都是针对统一认证服务器的数据库而言。
整个数据库分为三个部分:
forum论坛相关部分数据表,包括Forum Message等实体数据。
auth是统一认证服务器的数据表,包括上面四个字段的验证信息,Role信息。
user则是本系统的用户相关数据表,包括积分Reward;帖子调整等级Moderation。
关于userId序列号的获取,直接从本系统的数据表jiveID获取。
角色定义
角色目前定义四种:
管理员
版主
注册用户
非注册用户
角色在本系统在三个地方定义,这三个地方需要统一,如果修改就统一修改:
1. 在代码Role.java中定义
public interface Role {
/**
* Administrator 管理员
*/
String ADMIN =
"Admin";
/**
*注册用户
*/
String USER =
"User";
// 版主
String MODERATOR
="Moderator";
}
2.在数据表role中定义,role表中的值其实也是由Role.java定义保存
3.在web.xml中定义
角色在两个地方使用:
Web资源ACL配置:web.xml
组件ACL访问控制配置:jivejdon_permission.xml
基于数据的ACL只能在代码中实现,例如只有帖子所有者才可以修改自己的帖子,在ForumMessageService的updateMessage(EventModel em)加载了这种过滤方式。
注册和修改资料
配置struts的配置文件,struts-config-security.xml主要是配置用户帐户注册模块。
创建注册和修改用户资料的Jsp文件,Jsp视图页面对应account目录下Jsp,注意,这里用户资料的新增和修改是分开两个Jsp实现,account/EditAccountForm.jsp和account/ NewAccountForm.jsp,登陆验证是SignonForm.jsp
配置JdonFramework.xml的Model部分;
外部调用注册和修改功能URL是:
新注册:/account/newAccountForm.shtml
修改资料:/account/editAccountForm.shtml
基于容器的用户登陆
配置web.xml,使用Web容器的安全认证,将管理员和注册用户以及普通用户对不同Web资源访问权限通过web.xml配置实现,这个部分包括两部分:J2EE标准规定部分;各个容器的自己实现部分,具体配置文档见:
http://www.jdon.com/idea/jaas/06001.htm
基于JBoss容器的用户登录配置见:
http://wiki.jboss.org/wiki/Wiki.jsp?page=DatabaseServerLoginModule
http://www.informit.com/articles/article.asp?p=389111&seqNum=1&rl=1
下面谈一下标准规定部分的配置,适合所有服务器和容器。
在配置web.xml时,需要本系统有一个登陆Action,为了激活cookie自动登陆功能,这样可通过cookie实现SSO功能,在web.xml配置:
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.jdon.security.web.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
com.jdon.security.web.LoginServlet是jdon框架中一个类,该类主要是实现Cookie功能。
下面是配置容器安全认证的登陆功能。
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/account/login.jsp</form-login-page>
<form-error-page>/account/login_error.jsp</form-error-page>
</form-login-config>
</login-config>
在jboss-web.xml配置如下:
<jboss-web>
<security-domain>java:/jaas/SecurityRealm</security-domain>
</jboss-web>
同时配置Jboss/server/default/conf/login-config.xml即可。
如果需要激活cookie自动登录,那么LoginServlet配置需要增加两行:
<init-param>
<param-name>login</param-name>
<param-value>/account/login.jsp</param-value>
</init-param>
<init-param>
<param-name>logout</param-name>
<param-value>/account/logout.jsp</param-value>
</init-param>
然后,在web.xml的form-login-page改为LoginServlet:
<form-login-config>
<form-login-page>/login</form-login-page>
<form-error-page>/account/login_error.jsp</form-error-page>
</form-login-config>
登录信息共享
用户安全登陆后,如何将用户信息如用户信息共享给业务层和表现层。
业务层使用登录后的用户信息进行业务权限判断,例如修改某个帖子时,检查当前登录用户是否是该帖子的原作者(假设修改权限规则是:只有原作者才能修改帖子)。
表现层使用登录后的用户信息为了控制Jsp页面某些信息是否显示,例如,显示帖子时,只有当前登录用户是该贴作者时,才显示“编辑”按钮,或用户按“编辑”连接,进入后告知其无法修改本帖:不是本帖作者或其他原因。
下面来讨论这个两种信息共享的实现:
共享给业务层
用户安全登陆后,如何将用户信息如用户ID共享给业务逻辑系统?有两种方式:
第一种: 还有一种方式通过Jdon容器直接将登录信息注射到业务层的业务服务种,这时你的业务服务AccountServiceImp只要继承com.jdon.container.visitor.data.SessionContextAcceptable就可以,原理参见Jdon框架开发指南的“高级部分”。后面这种方式就无需再实现表现层代码,将表现层相关代码降低到最低点。JiveJdon3采取的是这种方案,见ForumMessageShell.java,下面章节将详细讨论。
共享给业务层后,业务层有两种使用方式,通过SessionContext获得。
第二种方式没有第一种简单易用,实际就是不使用Jdon框架容器提供的SessionContext功能,在其他地方做文章:在表现层调用业务层之前将登录信息作为参数传入: 因为用户登陆是使用web.xml配置容器登陆实现的,Jdon框架提供了com.jdon.security.web.UserPrincipal可和容器互动,可参考JdonNews实现原理,最后在Handler中执行下面代码:
User user =
(User) ContainerUtil.getUserModelAfterLogin(request);
if (user != null) {
News news
= (News) em.getModel();
news.setUser(user);
}
如果user为空,表示该用户没有正常登陆。这种方式是在业务层之外(业务层的客户端)将登录信息设置到相应的用户对象中。这种方式只适合一些简单应用。
共享给表现层
用户安全登陆后,不但要将用户信息传给业务层,还要在表现层界面有所区别,这样再具体Jsp页面显示时,可根据是否登录成功推出不同的页面内容,如何判断登录成功呢?通过request.getUserPrinciple();如果获得一个Principle说明基于容器登录通过,然后将这个Principle保存到session中。
但是,只是通过Principle的信息还不够,Principle只能告知用户的登录用户名,只有访问受权限保护(web.xml)设置的目录时,request.getUserPrinciple()才有值,这就对用户的操作有一个假定前提。另外,只有用户名Principle信息是不够的,例如:帖子是否需要显示“修改”按钮等,这些都涉及权限规则判断。因此,必须在Session中保存用户Account完整对象,这样才适合多种应用。
下面首先先谈谈JPetstore的解决方案,该方案是通过登录专门Action来保存整个Account的,所以该方案只不适合基于容器登录。
将accountForm保存到session中,注意不是单个Principle,在accountForm有一个字段authenticated表示该用户是否验证通过,然后,在Jsp时,如果验证通过,显示“修改注册资料”按钮,这是一个简单权限检验功能(和业务Model无关),在common/
IncludeTop.jsp这个页面中,有下列语句:
<logic:present name="accountForm"
scope="session">
<logic:equal
name="accountForm" property="authenticated"
value="true" scope="session">
<html:link
page="/forum/editAccountForm.shtml?action=edit"
paramId="userId" paramName="accountForm"
paramProperty="userId">
<html:img
border="0" page="/images/04.gif" />修改注册资料</html:link>
|
<html:link
page="/forum/signoff.shtml?method=signoff"><html:img
src="/images/logout.gif" width="17" height="17"
alt="退出"
border="0"/>退出</html:link>
</logic:equal>
</logic:present>
如果用户登录,显示修改注册资料和退出的按钮,这里是否登录是根据accountForm的authenticated字段来判断,只要设置authenticated值就可以控制界面。
这个方案中,关键是accountForm是怎么被设置到Session中去的?
这需要通过一个专门的SignAction,登录Action,如下代码:
public class SignAction extends
DispatchAction {
public ActionForward signon(ActionMapping mapping,
ActionForm form,
HttpServletRequest request, HttpServletResponse response) throws
Exception {
AccountForm accountForm = (AccountForm) form;
accountForm.setAuthenticated(false);
AccountService accountService = (AccountService) WebAppUtil.getService(
"accountService", request);
Account account = accountService.getAccount(accountForm.getUsername(),
accountForm.getPassword());
if
(account != null) {
try {
Debug.logVerbose("signon.. account:" + account.getUsername(),
module);
PropertyUtils.copyProperties(accountForm, account);
accountForm.setAuthenticated(true);
accountForm.setPassword(null);
。。。。
显而易见,这种方案用户登录Action必须自己实现,如果我们使用基于容器的登录方案,用户登录是提交到j_security_check,不能提交到我们自己指定Action了,所以JPetstore这个方案不适合JiveJdon3的基于容器登录。
既然基于容器登录就不能自己做Servlet或Action,但是我们可以实现自己的ServletFilter,在这Filter中,我们检查request.getUserPrinciple()结果是否不为空,如果是,说明该用户正在访问一个受权限保护的区域,而且已经通过容器的安全检查,那么我们根据结果Principle调用AccountService,获得Account完整对象,然后将Account保存到Session中。
在其他地方包括Jsp中只有做到:我们首先检查Session中是否有Account,而且authenticated是否被设置为真,如果没有,则认为当前用户没有通过登录验证;
使用ServletFIlter和在Jsp中include 一个security.jsp这两个方案相比,可以直接访问Service,而security.jsp只能写一些简单的Java代码,如果写太多业务层访问代码违背MVC的View简洁要求。
与ServletFilter相竞争的是Action/Servlet串联,就是一个Action结束后forward到另外一个Action,这样做好处是针对性强,ServletFilter适合所有的Action。
访问权限ACL
ACL一般考虑下面情况:方法操作是否有权限访问?资源数据是否有权限访问?数据资源或操作是否允许被显示?详细描述如下:
1. Web资源,主要是以Web URL为特征,对Web资源的Jsp/Servlet 图片等目录下全部资源实现授权访问。
2. 组件资源,同一个组件的不同方法实现对资源进行不同性质的操作,每种操作方法都需要实现授权访问,例如:A角色可以实现帖子创建,但不能实现帖子删除。
3. 模型资源,业务模型只能让特定角色一些访问。比如某个帖子模型只能让积分超过多少的用户访问。其中必须解决与该模型关联的子模型访问权限关系。
4. 是否需要显示,如果当前用户无权修改帖子,就无需在显示帖子是,输出“修改”链接字样。
解决方案:
1.
Web URL资源授权访问,采取基于容器的安全体系,配置web.xml
2.
组件方法授权访问。 采取AOP拦截器方式实现,见PermissionInterceptor
3.
模型资源访问。 采取显式代码实现,见ForumMessageShell
考察整个授权系统,对象生命周期看两种:
1.
全局Application级别的授权规则,如上面Web.xml配置,就是属于全局性质,这类可以用配置实现,组件方法访问授权也属于这一种,使用OperationAuthorization来表达,详细在下一章讨论。
2.
动态Session级别的授权,这类授权需要根据用户动态登录后,根据登录后该用户的角色,再配以动态规则,决定某些资源是否能被操作,例如帖子只有被帖子作者或管理员可以修改。使用ResourceAuthorization表达。
除第一种无需编程,只需配置外,其他都需要我们具体设计编程。下面首先谈谈第一种实现。再详细讨论后者。
Web URL资源访问权限
这是采取采取基于容器的安全体系,有关Web URL资源授权访问背景知识见:
http://www.jdon.com/idea/jaas/06001.htm。
在web.xml中配置Web URL资源访问授权权限:
<security-constraint>
<display-name>admin
security</display-name>
<web-resource-collection>
<web-resource-name>Admin input</web-resource-name>
<url-pattern>/admin/*</url-pattern>
</web-resource-collection>
<web-resource-collection>
<web-resource-name>forum admin</web-resource-name>
<url-pattern>/forum/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>Admin</role-name>
</auth-constraint>
<user-data-constraint>
<transport-guarantee>NONE</transport-guarantee>
</user-data-constraint>
</security-constraint>
以上配置如果用户访问http://localhost:8080/admin/下任何资源,将要求是Admin角色,Web容器(无论Tomcat/JBoss/Weblogic)都自动推出login.jsp页面要求用户登录。
基于Web容器的ACL只能实现URL资源的权限访问,但是并未实现Web容器中组件方法的访问权限限制,后者类似EJB的方法权限设置。
组件访问权限
要求:帖子修改首先是权限问题,只有帖子的发贴者可以修改。
每个帖子都有一个发贴者,这个发贴者用户的ID是从session中获得,假设该用户已经正常登陆并且将自己保存到session中。
本系统我们是采取基于容器的登陆机制,我们必须从request.getUserPrincipal();获得用户登陆信息,如果没有说明用户没有登陆。
在JdonNews这样应用系统中,我们是通过在Handler中将用户资料从UserPrincipal中取得,然后赋予业务系统Model中。
这样做的缺点我们必须因为这个目的特地做一个ModelHandler实现,如何简化代码,直接在服务层MessageService中实现这个用户信息获取呢?
我们使用Jdon框架1.3提供的SessionContext,让我们的业务类继承com.jdon.container.visitor.data.SessionContextAcceptable,这样Jdon框架就可以通过set SessionContext向我们的业务类中注射当前SessionContext,而业务类内部方法则可以通过SessionContext获得当前用户登陆的信息。
首先,我们来实现POJO组件的方法授权访问。主要是三步:
设定访问权限规则XML定义,例如:
<permission>
<service
ref="testService">
<method
name="createUser">
<role>Admin</role>
</method>
<method
name="updateUser">
<role>Admin</role>
<role>User</role>
</method>
<method
name="deleteUser">
<role>Admin</role>
</method>
</service>
</permission>
开发读取这个XML的解析器,并配置Jdon框架:
<pojoService
name="permissionXmlParser"
class="com.jdon.framework.test.service.PermissionXmlParser"
>
<constructor
value="test_permission.xml"/>
</pojoService>
开发拦截器PermissionInterceptor并配置myaspect.xml如下:
<aspect>
<interceptor
name="test_permissionInterceptor"
class="com.jdon.framework.test.service.PermissionInterceptor"
pointcut="pojoServices" />
</aspect>
myaspect.xml只要放在WEB-INF/classes目录下,Jdon框架会自动辨识。并且装载PermissionInterceptor。详细描述见:http://www.jdon.com/jdonframework/acl.htm
将我们自己的myaspect.xml告诉Jdon框架有两种方式,上面是简单的一种,这种相当于将myaspect.xml放在系统的classpath中,有时这种方式可能影响到其他项目的运行,因此我们采取第二种方式,放置在本项目的web.xml中:
<context-param>
<param-name>aspectConfigure</param-name>
<param-value>WEB-INF/myaspect.xml</param-value>
</context-param>
业务模型访问权限
修改帖子权限只有该贴的发布者或管理员。这种权限访问是数据资源访问,很难使用配置实现,只能通过代码实现,使用Proxy模式专门做一个数据授权检验类
现在我们决定使用什么模式实现这个过滤,是Decorator还是Proxy,在权限扩展性方面可能会对所有方法进行基于数据资源验证,所以,这里选用Proxy模式比较好。
在这个类中我们需要完成只有该贴发布者才能修改的功能:
if
(!(account.getRoleName().equals(Role.ADMIN) ||
(!account.getUserId().equals(modForumMessage.getAccount().getUserId())))){
logger.warn("not
the owner , no permission update Message!");
em.setErrors(Constants.NOPERMISSIONS);
return;
}
以上代码将加入这个Proxy类中,这样,权限验证和从业务逻辑分离出来。
下面一步是处理这个Proxy类与原来业务逻辑类的关系,一旦涉及到关系,无疑需要使用设计模式了,为了在ForumMessageSevice的众多子类中架构一个可动态扩展的结构,我们需要专门进行一下这种关系设计。
ForumMessageShell是一个Proxy,在该类中,委托ResourceAuthorization进行授权。
子模型访问权限
模型之间通常存在关联关系,当我们对一个模型进行权限授权以后,其子模型也必须被同样授权保护。
以帖子和帖子的上传图片为例子,这两者存在松散的关联的关系(如果是聚合关系,部分嵌入在整体内部,保护整体,部分就能保护),图片上传和帖子创建不是同时进行的,因此,必须分离考虑权限保护。
资源保护在CRUD流程中有两种形式:
1.
CRUD输出视图权限保护,没有权限不能完整输出用于CRUD的View视图,这方面必须使用ServletFilter或Action串联实现。
2.
接受View视图的CRUD请求,没有权限不能进行接受处理CURD请求,这部分在Service层使用Proxy或拦截器实现。
CRUD输出视图主要区分是创建页面和编辑页面,创建页面是一个空白,模型没有建立,这是就没有所谓资源权限控制,当然存在方法访问权限控制。
上传附件的视图保护:在UploadFileListAction中加一个authFilter授权检查方法,用以控制UpLoadFileForm中的authenticated字段,upload.jsp是否显示根据authenticated字段来判断。
由于上传附件都临时放在Session中,因此,最后保存到数据库是由其父模型ForumMessage调用的,而且ForumMessage已经被权限控制,所以,上述第2种就没有必要。
表现层授权控制
以上讨论的是业务层的授权实现,在表现层,我们需要根据权限显示或关闭一些显示,例如,显示帖子时,我们根据当前用户是否登录以及ResourceAuthorization一些特性,决定是否显示“编辑”按钮;同样,如果用户对某个帖子进行修改,修改第一步显示需要修改内容时,我们就要在Jsp页面中告知无权限修改(如确实无权限)。这些都是授权在表现层的延伸控制。
根据前面“用户登录信息共享给表现层”分析,我们可以使用ServletFilter或Action串联来实现,Action串联针对性强,本项目中,我们在显示帖子内容或进行帖子编辑时实现授权控制,这两个功能主要是在messageListAction和messageAction中实现的,那么我们针对这两个Action进行ActionFilter控制。
在com.jdon.jivejdon.presentation.filter.
MessageListAuthFilter中,我们首先是否登录判断,如果是,再委托ResourceAuthorization实现是否授权判断。使用MessageListForm中的authenticateds数组boolean来包装当前多个ForumMessage的权限情况。
注意,为什么不将权限信息放在ForumMessage中呢?
ForumMessage是一个Domain Model,而且被保存在缓存中,是一个全局对象,所有用户都会访问这同一个对象ForumMessage,如果我们更改这同一个对象的属性,会产生同时读取这个ForumMessage的用户得到相同权限信息,存在安全漏洞。
资源权限检查是一个动态Session检查,检查后的权限信息必须是业务Model和Session登录信息相互核对的结果。
MessageListAuthFilter代码如下:
public class
MessageListAuthFilter extends Action {
public ActionForward
execute(ActionMapping mapping, ActionForm form,
HttpServletRequest
request, HttpServletResponse response)
throws
Exception {
MessageListForm
messageListForm = (MessageListForm)form;
boolean[]
authenticateds = new boolean[messageListForm.getList().size()];
messageListForm.setAuthenticateds(authenticateds);
AccountService
accountService = (AccountService)
WebAppUtil.getService("accountService", request);
Account
account = accountService.getloginAccount();
if
(account == null){//没有登录
return
mapping.findForward("success");
}
ForumMessageService
forumMessageService = (ForumMessageService)
WebAppUtil.getService("forumMessageService", request);
Iterator iter =
messageListForm.getList().iterator();
int i = 0;
while(iter.hasNext()){
ForumMessage forumMessage = (ForumMessage)iter.next();
//检查该ForumMessage和当前Session中的用户是否一致,当前用户信息
//已经存在业务容器中的SessionContext中,见forumMessageService子类
boolean result = forumMessageService.isAuthenticated(forumMessage);
authenticateds[i] = result;
i++;
}
return
mapping.findForward("success");
}
}
在struts-config-message.xml中配置Action串联Filter关系:
<action
path="/forum/messageListBody"
type="com.jdon.jivejdon.presentation.action.MessageListAction"
name="messageListForm"
scope="request"
validate="false">
<forward
name="success" path="/forum/messageListAuth.shtml"/>
</action>
<action path="/forum/messageListAuth"
type="com.jdon.jivejdon.presentation.filter.MessageListAuthFilter"
name="messageListForm" scope="request"
validate="false">
<forward
name="success" path="/forum/messageList.jsp"/>
</action>
同样原理适合用户编辑帖子的MessageEditAuthFilter,Action串联见struts-config-message.xml
更多Jdon框架专题讨论
JdonFramework作为一个免费开源软件开发平台,可以商用开发大多数数据库应用软件和管理软件: 电子商务软件 在线教育软件 税务软件 Web快速开发软件 财务软件 购物车软件 医院帐务软件 crm software medical software 人事薪资软件payroll software 在线购物软件 销售软件 项目管理软件 房产不动产管理软件 生产软件 PDM软件 制造业软件 仓库软件 采购软件 进销存软件 危险源监控软件 物流软件 超市软件 银行软件 保险软件 汽车软件 医疗软件 电子软件 自动化软件 服装软件 烟草软件 分销管理软件 供应商管理软件
|