声明:没有将所有的源代码贴上,只是提供思路
摘要:进入二十一世纪,信息化发展潮流越来越快,很多企业(包括中小企业)为了节省企业成本,提高生产效率,加快企业发展步伐而进入企业信息化改造,对于整个社会企业的发展蓝图来讲,无疑是一件很好的事情。
但是随着企业的发展不断向前,信息化改造越来越高的要求变化,结合当前应用系统的发展,权限设计成了信息化改造的头等大事,因为权限问题出现的企业保密问题案例屡见不鲜。当然,对于中小企业来讲,比起大型企业要简单得多。
本文主要就中小企业的应用系统权限设计做一个实现。
关键词:权限、角色、用户、Powerdesigner、Mysql、Java、Spring、Hibernate
一、权限部分模型简单分析
1、单位:即业务应用系统中的单位;
2、部门:即业务应用系统中的部门;
3、用户:即业务应用系统中的单位人员,一个单位人员可能属于多个角色roles,但是只属于一个具体的单位;
4、角色:对登陆进应用系统的用户进行一个actor分组,,一个角色可能包括多个单位人员、享受多种资源、查看多个菜单;
5、资源:一种资源也可能分属于不同的角色分类;
6、菜单:一个菜单也可能被不同的角色查看。
二、数据库模型设计建立
本应用系统的权限设计的数据库模型建立采用当前流行的工具Powerdesigner,与Rose的优缺点比较文章,推荐文章网站:http://npsajax.javaeye.com/blog/100684。
下面主要简单介绍Powerdesigner15.1版本建立CDM建模过程:
1.打开数据库建模工具Powerdesigner系统,建立命名空间“Workspace”和工程项目“Project”,右键单击工程项目在“New”子菜单下选择“Conceptual Data Model”菜单,如图(1)。
图(1):建立ConceptualDataModel
2.在“Conceptual Data Model”的工作区的Palette面板中选择实体图标 ,然后在工作区单击即可出现实体图例,如图(2)。
图(2):建立实体
3.双击Entity即可打开该实体的属性框,如图(3),在图(3)中的General选项卡中,Name表示要建立实体的名称,在Code表示该实体的代码,鉴于中文不是很好转换的问题,推荐Name和Code一般用英文的形式,Comment表示对该Name的一个说明,如User表。
图(3):实体General
在图(3)中选择Attribute选项卡,则展示实体的属性,比如图(4)中显示的User属性,即对应数据库中的表的字段,有Name、Code、DataType、Length、M、P、D等属性,其中M表示该属性不为空,P表示该属性标示为主键、D表示在实体图中显示该属性。
图(4):实体属性
在图(3)中选择Identifiers选项卡,如图(5)中显示的主键标示,在User中采用UserId
图(5):主键标示
4.在图(5)中设置完成后,点击图中的确定。则在工作区可以显示建立完成的实体图形。如图(6)
图(6):完整的实体
5.采用同样的方法,建立角色Role实体图,如图(7)
图(7):User和Role
6.下一步为User和Role实体建立关联关系,点击图标 ,然后从User实体拖拽到Role实体,并双击该关联,弹出如图(8)的关系。在General选项卡中,在“Name”和“Code”属性中填写“user_role”,Powerdesigner工具将默认“user_role”为User和Role多对多关系对照的中间表名称
图(8):关联关系属性框
在图(8)中选择Cardinalities选项卡,在其中可以选择两个实体的关系,如:One-One、One-Many、Many-One、Many-Many等。同时可以标示两个实体之间的关系,如在“User to Role”中可填写“belongs”,表示:一个用户属于一个角色。“Role to User”中可填写“include”,表示:一个角色包含一个用户。如图(9)所示
图(9):实体关系
待两个实体关系建立完整后,则在工作区显示为如图(10)。
图(10):完整的关联关系(多对多)
7.在建立完整的关联关系图后,点击“Tools”中的“Generate Physical Data Model”菜单则表示将概念数据模型转换成物理数据模型(Physical Data Model),如图(11)
图(11):选择生成物理数据模型菜单
8.在General选项卡中选择“Generate Physical Data Model”菜单之后,弹出如下的Options框,需要进行一些设置,选中“Generate new Physical Data Model”表示生成一个新的物理数据模型,选择相应的数据库MySQL5.0,如图(12)
图(12):转换属性设置
在图(12)中,还有详细设置选项卡“Detail”和目标模型选项卡“Target Models”,一般不需要设置。在“Selection”选项卡中,选择要转换的实体,如下图(13)中选择了Usre和Role实体
图(13):实体选择设置
9.在图(13)中设置完成后,点击“确定”按钮,则会生成物理数据模型图,如在图(14)中的物理数据模型工作区中可以看到完整的物理数据模型,包括中间表的自动设计
图(14):物理数据模型
10.建立数据源,Powerdesigner工具支持ODBC和JDBC两种连接数据库的方式,连接设置也可以通过文件进行配置。如图(15)中选择ODBC连接mysql数据库。
图(15):建立数据源
在图(16)中展示了如何对连接进行详细配置参数,如下图:
图(16):连接详细配置
11.在连接好数据库之后,需要将现在的物理数据模型生成数据库中的对应表结构。选择“DataBase”下的“Generate DataBase”菜单,如图(17)。
图(17):菜单选择
在选择菜单之后会弹出如下的“Database Generation”窗口,如图(18)所示。在General选项卡中,选择单选按钮“Script generation”则只是在选择的路径下生成执行的SQL脚本文件,选择单选按钮“Direct generation”则直接生成到所选择的数据库,同时生成编辑脚本。
选择复选框“Check model”表示在转换的过程中需要设置检查模型,当然检查的字段项可以需要自己设置。
图(18):转换设置
选择“Options”选择项卡,可以进行详细的检查设置,包括表和字段。如图(19)
图(19):转换操作详细设置
选择“Selection”选项卡,选择要转换的表结构,如图(20)
图(20):选择要转换表结构
选择“Summary”选项卡,可以看到各选项卡的详细设置,如图(21)
图(21):详细设置
选择“Preview”选项卡,可以预览生成表结构和SQL的脚本语句,如图(22)。
图(22):SQL语句预览
12.转换成功并添加测试数据后,打开MySQL数据库客户端浏览软件可以查询,从图(23)可以知道表结构已经建立完成,如图(23)所示
图(23):MySQL客户端查询
13.同时Powerdesigner也可以将物理数据模型转换成逻辑数据模型(LDM)、面向对象模型(OOM)等,对于转换成Java代码、API样式的文档、报表也是很不错的。当然也可根据大概数据量直接分析数据库将来的数据文件大小、逆反工程等等。在此不再表述。
三、简单介绍集成开发应用实例
1、集成开发环境:
Eclipse3.3.2、Tomcat6.0.8、JDK1.6、MySQL5.0、Powerdesigner15.1、Spring3.0、SpringMVC
2、权限设计部分数据库模型:
⑴.单位(Unit)
⑵.部门(Dept)
⑶.用户(User)
⑷.角色(Role)
⑸.资源(Resource)
⑹.菜单(Menu)
3、权限部分数据库表结构模型建立:
⑴、建立概念数据模型(CDM),如图(24)。
图(24):权限设计部分概念数据模型
⑵、将概念数据模型图转换成物理数据模型图(PDM),如图(25)
图(25):权限设计部分物理数据模型
4、应用系统框架实现
⑴.首先在web.xml里面添加平台filter配置以及servlet注册与监听文件加载配置:
securityAjaxFilter
com.sunline.framework.core.filter.SecurityAjaxFilter
ajaxRequestTokens
X-Requested-With=XMLHttpRequest
securityAjaxFilter
/*
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
contextConfigLocation
/WEB-INF/applicationContext*.xml
org.springframework.web.context.ContextLoaderListener
DelegatingFilterProxy是一个Spring Framework的类,代理一个applicationContext-security.xml中定义的Spring bean所实现的filter。
⑵.编辑applicationContext-security.xml中的安全服务配置元素,如
为了加强登陆用户的管理,特别增加了用户单独登陆的配置,即:
从配置中可以得知:登录页面访问权限是匿名登录,即
从中可以看出,登录成功的页面只有角色为“ROLE_ADMIN”的登录用户才能查看,否则登录之后的页面始终会跳转到登录页面位置。
接下来看标签,其中的配置项:
①.login-page:表示定义开发人员自己的登录页面;
②.authentication-failure-url:表示权限认证失败所跳转到的目标页面;
③.default-target-url:表示用户登录成功的页面;
④.login-processing-url:表示用户登录是所发的请求;
⑤.always-use-default-target=true:表示
一直跳转到这一页
在实际操作中,登录时“default-target-url”配置表示向服务器发送一请求“/index.htm”,在系统的控制层返回默认的登录成功的页面名称,下面是登录转发的控制层程序片段:
package com.sunline.framework.core.controller;
/**
* @author:limingzhong
* @date:2012-3-29
* @class:WEB业务控制类
*/
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
@RequestMapping("/index.htm")
public void index(){
//loads the tile for index.jsp
}
}
根据上面的程序,可以看得出,是通过SpringMVC的注解功能实现的,针对.htm的请求,首先会经过web.xml中分配器配置对请求的类别进行识别,如图(30)
controller
org.springframework.web.servlet.DispatcherServlet
1
controller
*.htm
json
org.springframework.web.servlet.DispatcherServlet
1
json
*.json
该配置实现了两个分配器,一是htm,二是json。所以会把以.htm结尾的请求发送到controller-servlet.xml配置文件中进行处理。在系统中的controller-servlet.xml中有如下的配置,如图(31):
其中图(31)中的注解介绍:
①.对controller包下的类进行扫描,完成创建bean和自动注入依赖的功能;
②.启动SpringMVC的注解功能,以完成请求和注解POJO的功能;
③. 对模型进行解析,即在返回的模型名称中加上前后缀
⑶.启动Tomcat服务器,在浏览器地址栏输入http://127.0.0.1:8088/sunline,点击回车键即进入登录页面,在如图(32)
图(32):简单的登录页面
从图中可以看出,只要在文本框中输入合法的登录信息(用户名与密码),就可以进入该系统进行操作功能。
⑷.下面介绍数据库方面的设计和配置:
该应用系统是在关系型数据库的基础上实现的,要从数据库获取用户名,就必须建立于数据库的连接。
建立数据库配置文件database.properties,如图(33)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/sunline
jdbc.username=root
jdbc.password=admin
hibernate.dialect=org.hibernate.dialect.MySQLDialect
建立文件applicationContext-database.xml,并进行数据库连接配置
/WEB-INF/database.properties
要使用Hibernate3.0,必须进行sessionFactory信息配置
${hibernate.dialect}
org.hibernate.cache.EhCacheProvider
classpath:frameDept.hbm.xml
classpath:frameMenu.hbm.xml
classpath:frameResource.hbm.xml
classpath:frameRole.hbm.xml
classpath:frameUnit.hbm.xml
classpath:frameUser.hbm.xml
com.sunline.framework.core.bean
com.sunline.rmca.core.bean
为了让用户在操作的过程中,始终保持数据的正确性和有效性,必须进行数据库操作事务配置
为了让系统用户的信息更加安全和保证系统的有效人员登录,在实际的用户表设计中会有一个登录名和一个用户名,同时密码需要经过加密处理,建立一个applicationContext-service-security.xml文件,其中的加密处理
此图中配置了两种加密方式,一种是MD5方式,一种是盐值的方式
用户登录权限认证配置
在用户权限认证配置中,可以知道对用户的密码同时采用了两种方式的加密处理,即:MD5+盐值。MD5算法不可逆,但是对同一字符串计算的结果是唯一的,所以为了防止一些人采用字典攻击的方式来破译密码,所以进行了盐值处理。
盐值的原理很简单,即先把密码和盐值指定的内容合并在一起,再使用MD5对合并后的内容进行演算,这样攻击者不知道盐值的值,也很难反算出密码原文。从图(37)和图(38)可知,本系统将每个用户的username作为盐值。
⑦.利用SpringSecurity的投票器为登录用户授权
从配置图中可以看出,只有角色以“ROLE_”开头的用户才能够进行投票并操作该业务应用系统。至此,关于用户信息的基本配置已经结束。
⑸.下面介绍提取登录用户的信息
①.首先对用户的登录名和密码进行提取
图(40):登录界面信息
从图中可以知道,用户的登陆名称是“admin”,那么怎么去数据库里面根据用户名称来验证用户输入的名称和密码呢?
②.下面来了解用户管理类UserdetailsServiceImpl.java程序片段
package com.sunline.framework.core.filter;
import com.sunline.framework.core.bean.FrameRole;
import com.sunline.framework.core.bean.FrameUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
@Service("userDetailsService")
@Transactional(readOnly = true)
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private SecurityEntityManager securityEntityManager;
//获取user实例
public UserDetails loadUserByUsername(String userLogin) throws UsernameNotFoundException, DataAccessException {
FrameUser user = securityEntityManager.findUserByLoginName(userLogin);
if(user == null)
throw new UsernameNotFoundException(userLogin + " 不存在");
Collection grantedAuths = Arrays.asList(obtainGrantedAuthorities(user));
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
return new org.springframework.security.core.userdetails.User(user.getUserLogin(),user.getPassword(),enabled,accountNonExpired,credentialsNonExpired,accountNonLocked,grantedAuths);
}
//获取权限
private GrantedAuthority[] obtainGrantedAuthorities(FrameUser user) {
Set authSet = new HashSet();
for (FrameRole role:user.getRoles()) {
authSet.add(new GrantedAuthorityImpl(role.getRoleName()));
}
return authSet.toArray(new GrantedAuthority[authSet.size()]);
}
}
该程序片段表示根据登录名称提取一个登录人的有效信息(具体提取用户信息的方式采用Hibernate,鉴于方便性,故不在此表述),并给予了提取出来用户的权限。
③.当然光有这点程序片段是不行,
在⑷中⑥的xml配置片段中,我们可以看到该配置用到了userDetailsService的映射,说明这两个程序是联合配置使用的。当启动userDetailsService注入的时候,会直接执行用户查询的动作。
以上程序联通起来的执行顺序就是:当一个用户输入用户名和密码,那么通过加密算法计算之后,看是否与从数据库中提取出来的用户信息(此处只关系登录名和密码)是否一致。如果不成功,则跳转到登录页面“/login.jsp”,成功则跳转到我们指定的页面“/index.htm”。
⑹.登录成功应该做的工作
在实际的项目中,我们的用户在操作的时候,经常是根据用户的权限操作自己的数据,也就要求我们的应用系统在用户登录系统之后和登出系统之前,系统的Session会话不能改变,同时需要在页面上能够获取到该登录用户的信息,这些功能SpringSecurity都已经做得很好了。
下面简单介绍一下如何在页面上渲染当前用户的信息:
SpringSecurity标签,使用须先在.jsp文件里声明这些标签库:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
然后采用下列方式就能渲染用户信息了
当然,要想登录成功,并获取到登录人的信息,前提是该登录用户的认证必须要获得成功才行。
登录成功页面
图(45):登录成功页面
④.成功登录系统后,即可对当前用户进行角色配置,对角色进行菜单、资源绑定方面的工作。简单的说,即是向中间表结构中添加多对多的关系。当然要添加这些关系,要靠Hibernate来工作了。
在应用系统的框架里面,对于表结构之间多对多的级联关系,生成表与Spring注入的时候是依靠对类的注解实现的,查询的时候是采用hql和注解实现的。
在Eclipse里面,注入bean的方式是采用配置.xml文件来实现的。
建立数据库的XML映射文件,配置如组合图(46):
unit.hbm.xml:
dept.hbm.xml:
user.hbm.xml:
role.hbm.xml:
resource.hbm.xml:
menu.hbm.xml:
通过这些就可以实现Unit对Dept、Dept对User、User对Role、Role对Menu、Role对Resource的关系之间的级联操作了。关于配置table.hbm.xml文件的加载,需要在applicationContext-database.xml文件的sessionFactory配置中进行配置
classpath:frameDept.hbm.xml
classpath:frameMenu.hbm.xml
classpath:frameResource.hbm.xml
classpath:frameRole.hbm.xml
classpath:frameUnit.hbm.xml
classpath:frameUser.hbm.xml
⑤.授权----方法的授权、Url的授权配置
第⑤点暂时在dhcca3.0版本中不太会用到涉及,程序片段省略。
⑥.登录成功后的常规操作、Ext页面改写、以及不涉及到权限的配置、缓存配置等功能实现配置省略四、附录
1、分享几种权限常规设计
⑴.基于角色的权限设计
这种方案是最常见也是比较简单的方案,不过通常有这种设计已经够了,所以微软就设计出这种方案的通用做法,这种方案对于每一个操作不做控制,只是在程序中根据角色对是否具有操作的权限进行控制;这里我们就不做详述
图(49):基于角色的权限设计
⑵.基于操作的权限设计
这种模式下每一个操作都在数据库中有记录,用户是否拥有该操作的权限也在数据库中有记录,结构如下:
图(50):基于操作的权限设计
⑶.基于角色和操作的权限设计
图(51):基于角色和操作的权限设计
如上图所示,我们在添加了Role,和RoleAction表,这样子就可以减少UserAction中的记录,并且使设计更灵活一点。但是这种方案在用户需求的考验之下也可能显得不够灵活够用,例如当用户要求临时给某位普通员工某操作权限时,我们就需要新增加一种新的用户角色,但是这种用户角色是不必要的,因为它只是一种临时的角色,如果添加一种角色还需要在收回此普通员工权限时删除此角色,我们需要设计一种更合适的结构来满足用户对权限设置的要求。
⑷.组合的权限设计,其结构如下
图(52):组合的权限设计
我们可以看到在上图中添加了UserAction表,使用此表来添加特殊用户的权限,改表中有一个字段HasPermission可以决定用户是否有某种操作的权限,改表中记录的权限的优先级要高于UserRole中记录的用户权限。这样在应用程序中我们就需要通过UserRole和UserAction两张表中的记录判断权限。
到这儿呢并不算完,有可能用户还会给出这样的需求:对于某一种action所操作的对象某一些记录会有权限,而对于其他的记录没有权限,比如说一个内容管理系统,对于某一些频道某个用户有修改的权限,而对于另外一些频道没有修改的权限,这时候我们需要设计更复杂的权限机制。
⑸.对于同一种实体(资源)用户可以对一部分记录有权限,而对于另外一些记录没有权限的权限设计
图(53):部分权限设计
对于这样的需求我们就需要对每一种不同的资源创建一张权限表,在上图中对Content和Channel两种资源分别创建了UserActionContent和UserActionChannel表用来定义用户对某条记录是否有权限;这种设计是可以满足用户需求的但是不是很经济,UserActionChannel和UserActionContent中的记录会很多,而在实际的应用中并非需要记录所有的记录的权限信息,有时候可能只是一种规则,比如说对于根Channel什么级别的人有权限;这时候呢我们就可以定义些规则来判断用户权限,下面就是这种设计
⑹.涉及资源,权限和规则的权限设计
图(54):涉及资源,权限和规则的权限设计
在这种设计下角色的概念已经没有了,只需要Rule在程序中的类中定义用户是否有操作某种对象的权限
⑺.根据目前我们的客户对权限设计的需求来看,我们只要将我们自己的权限控制设计与上面第3种设计模式相结合,就可以达到客户的权限设计要求,即:基于角色、资源的权限控制设计,如下图(55):(本系统权限设计思想)
图(55):SUNLINE权限设计
⑻.推荐一篇基于应用系统权限设计权利文章:
http://jiaoyingjie.javaeye.com/blog/276055。