一、Spring Security介绍
Spring Security的前身Acegi,其配置及使用相对来说复杂一些,因为要配置的东西比较多,Sprng Security简化了以前的配置。大家有兴趣可以多多了解以前的版本,因为很多细节在前面的版本可以看得比较清楚,后面的版本需要看源代码才知道其实现的原理了。
基于角色的设计与实现是绝大部分系统中比较常见的权限管理方式,对权限进行分组进行管理有助于减少权限管理的复杂程度。
Spring Security目前提供了三种权限的管理方式:
一、 基于URL的拦截方式
二、 基于方法的AOP拦截方式
三、 基于数据的权限拦截方式
第一种是常见的权限管理方式,第二种有时也可以通过第一种去进行实现,方法级的拦截容易实现,不过要以友好的方式显示权限不足设计及实现就有些啰嗦。基于数据级的安全拦截实现上得更麻烦,在此不作介绍,我们只是针对第一种方式作设计。Spring Security在URL上已经提供了比较好的管理,不过其是以类似以下这种方式进行配置的。
这种要动态实现角色与权限的管理就显得有些不足了,因此需要进行扩展实现,我们可以把一些不需要进行安全拦截的url放在Spring的以上配置中,设置filters=”none”,如images,css,js等,提高访问的速度。
在扩展Spring Security之前,我们需要了解一下Spring Security的相关术语。
Authentication (认证)对象
其实就是一个可以通过Spring Security的认证的身份证明。如实现该接口的类UsernamePasswordAuthenticationToken,表示可以通过username及password作为身份验证。
Authentication对象包含了
- Principal 标识是哪一个对象,可以认为是用户
- Credentials 信任的对象,如密码。
- Authorities 权限的集合,在我们的系统中可以认为是角色的集合 (authorities要赋予给principal的)
SecurityContextHolder
是Spring Security的核心对象,是安全上下文的访问的入口。如取得当前的登录用户可以从该类中的相应的方法取得。该类中包含ThreadLocal私有属性 用于存取SecurityContext, SecurityContext包含Authentication私有属性。如实现弹出窗口登录功能的时候,输入的用户名及密码并没有最终经过SPRING SECURITY的filter,那么如何使用得当前用户可以成功登录呢,其就是利用到这一点,通过该类拿到SecurityContext,然后设置一个认证的对象给它,SPRING SECURITY在看到该认证对象的时候,就会成功经过了身份的认证了。
其实也可以这样理解,为了处理Http请求间认证,Spring Security使用HttpSessionIntegrationFilter,HttpSessionIntegrationFilter用于在 HttpSession存储Http请求间的SecurityContext。不过我们可以通过SecurityContextHolder去拿到这个 SecurityContext
AuthenticationManager
通过Providers 验证 在当前 ContextHolder中的Authentication对象是否合法。
AccessDecissionManager
经过投票机制来审批是否批准操作
Interceptors(拦截器)
拦截器(如FilterSecurityInterceptor,JoinPoint,MethodSecurityInterceptor等)用于协调授权,认证等操作。
Spring Security是Spring中 一个强大的安全管理框架,不过目前在我们系统中使用的仅是其中一部分的功能,则权限过滤安全检查的功能。如果抛开这个框架,我们实现权限管理的时候,可能 使用最多的方案还是使用Filter来进行过滤,在Filter里判断当前的用户是否为登录用户,若是登录用户,则看是否有权限访问当前的资源,若为未登 录用户,则跳至登录页面。
二、扩展Spring Security
扩展Spring Security基于角色的管理策略,通过角色分配,保证系统的安全。其安全的手段包括以下:
1. 登录时需要加上验证码
2. 所有的数据展示及访问页需要登录后才能访问
3. 用户的数据库密码存储时使用Sha-256的加密算法
4. 登录后的所有系统的访问URL均需要授权
5. 登录多少次失败后,可锁定IP,约20分钟后才能自动解锁。(尚未实现)
权限设计目前是采用基于角色控制的方式,用户需要访问系统的资源,首先必须要授予一个角色,而该角色具有访问系统资源的权限的能力,也可以认为是权限的集 合。因此,一个用户要访问系统的某个资源(如产品列表),则首先要授予一个能够访问产品列表资源的角色(如productAdmin)。只要任一个用户拥 有了该角色,即可以访问该资源。
系统的安全涉及到两个不同的概念,认证和授权。前者是关于确认用户是否确实是他们所宣称的身份。用户进入系统的时候,首先要进行第一个操作就是进行身份认 证,即Authentication。在系统中一般表现为用户用账号跟密码登录。如果都正确了,则可以登录系统。在现实中你可以这样理解,员工在进入公司 之前,需要进行身份的确认。身份确认通过后,则可以进入公司。进入公司后,并不代表可以随便进入公司的每个办公室。这时就需要每个看当前员工具有哪些角 色,即授权。授权则是关于确认用户是否有允许执行一个特定的操作。如当前员工是总经理,则可以进入总经理办公室,并且可以进入普通员工的办公区域。是因为 总经理已经授权可以出入这些地方。
在本系统中,权限表现为功能菜单及系统访问的URL。
如:
添加用户,其访问的url为 /system/saveAppUser.do
删除用户,其访问的url为/system/deleteAppUser.do
查询用户,其访问的url为/system/listAppUser.do
因而用户、角色、权限之间的关系可以用如下的图描述:
表设计
一个用户可以有多个角色,每个角色有多个功能菜单,每个功能菜单会对应多个系统访问的URL
表设计如下所示:
表说明:
1. app_user系统用户表,放置系统的所有用户
2. user_role用户角色,放置系统的所有角色
3. app_role角色表,放置用户角色
4. role_fun角色对应的功能表,放置角色拥有的功能
5. app_function系统的功能表,放置系统参与授权的所有功能
6. fun_url系统的功能对应的权限URL表
目前我们需要扩展Spring Security的以下两部分功能
1. 身份认证
2. 授权
Spring Security是由一组的filter来进行统一的过滤,不同的filter进行相应的权限过滤功能。不过在Security跟spring集成的过程中,其是由一个代理的类进行这些filter的统一管理。可以在web.xml中进行了查看,如下所示:
所有经过springSecurityFilterChain的url,都会转到DelegatingFilterProxy类的bean去处理。而该Bean在Spring Security 2.0中,已经内置于安全管理的缺省的配置当中,我们只需要把app-security.xml加入我们系统管理中来即可。如下:
p
身份认证
说明:当用户登录时,会根据用户账号及密码进行验证,验证由authenticationManager来进,其会调用实现UserDetailsService接口实现类完成,在本系统,是由appUserDaoImpl类来实现。
而我们的用户及角色实体要成为安全框架识别的安全实体,需要相应实现不同的接口,如下所示:
访问授权
授权的管理是通过Filter来进行的,用户访问URL时,均需要经过Spring Security的URL进行授权。在本系统中,这个功能是通过SecurityInterceptorFilter来进行。
系统启动时,会把所有的权限以[角色—URL列表]的形式放置在一个全局的Map中,用户访问系统的url时,就会根据当前用户所拥有的角色是否包含此 URL。这个全局的权限匹配源则由SecurityDataSource来提供。由于登录用户在进入系统后,都会具备一些常用的功能,所以每个用户均有一 个PUBLIC_ROLE的角色,代表可以访问系统的公告资源。该角色对应的可访问的URL,则配置在SecurityDataSource Bean中的publicUrls属性中。
SecurityInterceptorFilter代码如下所示:
三、EXT的扩展实现
至此,我们完成了对Spring Security的权限扩展,但是EXT访问的时候,我们的应用程序都是在一个页面上进行,也就是我们之前说的One Application One Page,几乎所有的请求都是通过Ajax的请求来时行,页面没有刷新,当权限不足的时候,我们如何提示用户呢?另外我们的功能菜单又是如何来根据用户的 角色来显示出来呢?在此,我们把需要把角色、功能、权限URL需要进行统一管理。我们从以下几个方面来进行扩展。
当用户权限不足时,我们需要提示用户无限访问该URL,在app-sercurity.xml中,我们配置了以下:
这个XML文件会在应用程序启动添加至系统的全局变量中,以“角色”对应“URL”的Map提供数据源来进行。
那么角色对应的URL是如何来构造的,这个相对简单一些,以上的功能及菜单,其均存在一个Id,如角色设置(AppRoleView),添加角色“_AppRoleAdd”。
每个角色就保存这些ID,所以加载这些ID,就有办法把其下的URL全部加载出来,从而形成角色与URL的映射关系。
另外,我们还可以把用户所拥有的权限,通过该用户拥有哪些角色,每个角色包括哪些权限的ID,从而构造出该用户的权限集合。如下所示,当用户登录后,我们 把所有的ID集中放在用户的rights字段中,这样就可以通过ID来决定用户是否有权限访问某个功能按钮,从而达到功能级别的控制,如:
- //加载权限
- Ext.Ajax.request({
- url:__ctxPath+'/system/getCurrentAppUser.do',
- method:'Get',
- success:function(response,options){
- var object=Ext.util.JSON.decode(response.responseText);
- //取得当前登录用户的相关信息,包括权限
- curUserInfo=new UserInfo(object.user.userId,object.user.fullname,object.user.rights);
- }
- });
- //加载权限
- Ext.Ajax.request({
- url:__ctxPath+'/system/getCurrentAppUser.do',
- method:'Get',
- success:function(response,options){
- var object=Ext.util.JSON.decode(response.responseText);
- //取得当前登录用户的相关信息,包括权限
- curUserInfo=new UserInfo(object.user.userId,object.user.fullname,object.user.rights);
- }
- });
以下为user.rights的构造,是在用户登录的时候进行配置实现,为AppUserDaoImpl.java的部分代码
- public UserDetails loadUserByUsername(final String username)
- throws UsernameNotFoundException, DataAccessException {
- return (UserDetails) getHibernateTemplate().execute(
- new HibernateCallback() {
- public Object doInHibernate(Session session)
- throws HibernateException, SQLException {
- String hql = "from AppUser ap where ap.username=? and ap.delFlag = ?";
- Query query = session.createQuery(hql);
- query.setString(0, username);
- query.setShort(1, Constants.FLAG_UNDELETED);
- AppUser user = null;
- try {
- user = (AppUser) query.uniqueResult();
- if (user != null) {
- Hibernate.initialize(user.getRoles());
- Hibernate.initialize(user.getDepartment());
-
- //进行合并权限的处理
- Set<AppRole> roleSet=user.getRoles();
- Iterator<AppRole> it=roleSet.iterator();
-
- while(it.hasNext()){
- AppRole role=it.next();
- if(role.getRoleId().equals(AppRole.SUPER_ROLEID)){//具有超级权限
- user.getRights().clear();
- user.getRights().add(AppRole.SUPER_RIGHTS);
- break;
- }else{
- if(StringUtils.isNotEmpty(role.getRights())){
- String[]items=role.getRights().split("[,]");
- for(int i=0;i<items.length;i++){
- if(!user.getRights().contains(items[i])){
- user.getRights().add(items[i]);
- }
- }
- }
- }
- }
-
- }
- } catch (Exception ex) {
- logger.warn("user:" + username
- + " can't not loding rights:"
- + ex.getMessage());
- }
- return user;
- }
- });
- }
- public UserDetails loadUserByUsername(final String username)
- throws UsernameNotFoundException, DataAccessException {
- return (UserDetails) getHibernateTemplate().execute(
- new HibernateCallback() {
- public Object doInHibernate(Session session)
- throws HibernateException, SQLException {
- String hql = "from AppUser ap where ap.username=? and ap.delFlag = ?";
- Query query = session.createQuery(hql);
- query.setString(0, username);
- query.setShort(1, Constants.FLAG_UNDELETED);
- AppUser user = null;
- try {
- user = (AppUser) query.uniqueResult();
- if (user != null) {
- Hibernate.initialize(user.getRoles());
- Hibernate.initialize(user.getDepartment());
-
- //进行合并权限的处理
- Set<AppRole> roleSet=user.getRoles();
- Iterator<AppRole> it=roleSet.iterator();
-
- while(it.hasNext()){
- AppRole role=it.next();
- if(role.getRoleId().equals(AppRole.SUPER_ROLEID)){//具有超级权限
- user.getRights().clear();
- user.getRights().add(AppRole.SUPER_RIGHTS);
- break;
- }else{
- if(StringUtils.isNotEmpty(role.getRights())){
- String[]items=role.getRights().split("[,]");
- for(int i=0;i<items.length;i++){
- if(!user.getRights().contains(items[i])){
- user.getRights().add(items[i]);
- }
- }
- }
- }
- }
-
- }
- } catch (Exception ex) {
- logger.warn("user:" + username
- + " can't not loding rights:"
- + ex.getMessage());
- }
- return user;
- }
- });
- }
JOffice中的权限管理是基于角色的管理策略,采用Spring Security2的配置方式,同时能够结合EXT3来进行整个系统的权限管理,通过使用配置文件,进行整个系统的功能集中管理,包括系统左边的导航菜 单,数据列表中的功能操作权限。我们知道,在传统的Web项目中,我们都是采用基于URL进行权限控制的。基于EXT的应用也不例外,只不过我们是需要结 合前台的功能菜单一起,前台显示出来的功能菜单,其后台均代表可以访问。
使用方法:
一、先在menu.xml中配置系统的功能,如我们配置角色管理的权限,如下所示:
当然,这种还是要避免出现,因为系统在客户端出现的功能菜单,均是有权访问的。
3.前台的代码配置
前台的用户登录系统后,会有一个全局的变量存储用户的所有权限(其会把所有的角色的配置抽取出来,去掉重复的权限配置),可以在客户端直接检查用户是否有权限访问某一个菜单的功能,如我们检查用户是否直接有访问“添加角色”的功能,则可以调用系统提供的方法:
isGranted(‘_AppRoleAdd’);
该函数返回为true则代表可以访问。
因此我们若要进行更高的权限粒度控制,我们需要把原来的代码进行改装一下。如:
AppRoleView.js需要进行更改。
修改一:
Java代码
- var toolbar = new Ext.Toolbar({
- id : 'AppRoleFootBar',
- height : 30,
- bodyStyle:'text-align:left',
- items : []
- });
- if(isGranted('_AppRoleAdd')){
- toolbar.add(new Ext.Button({
- iconCls : 'btn-add',
- text : '添加角色',
- handler : function() {
- new AppRoleForm();
- }
- }));
- }
- if (isGranted('_AppRoleDel')) {
- toolbar.add(new Ext.Button({
- iconCls : 'btn-del',
- text : '删除角色',
- handler : function() {
- var grid = Ext.getCmp("AppRoleGrid");
- var selectRecords = grid.getSelectionModel().getSelections();
-
- if (selectRecords.length == 0) {
- Ext.Msg.alert("信息", "请选择要删除的记录!");
- return;
- }
- var ids = Array();
- for (var i = 0; i < selectRecords.length; i++) {
- ids.push(selectRecords[i].data.roleId);
- }
- AppRoleView.remove(ids);
- }
- }));
- }
修改二
管理列中的栏目功能:
Java代码
- if(isGranted('_AppRoleDel'))
- str = '<button title="删除" value=" " class="btn-del" onclick="AppRoleView.remove('+ editId + ')"></button>';
- if(isGranted('_AppRoleEdit'))
- str += ' <button title="编辑" value=" " class="btn-edit" onclick="AppRoleView.edit('+ editId + ')"></button>';
- if(isGranted('_AppRoleGrant'))
- str += ' <button title="授权" value=" " class="btn-grant" onclick="AppRoleView.grant('+ editId + ',\'' + roleName + '\')"> </button>';
二、同步menu.xml的数据至数据库
打开配置common/config/下的config.properties的这个配置,如下:
isSynMenu=true
打开此配置则表示会把menu.xml中的所有的Function中的url同步到数据库,转给Spring Security进行匹配,则此时进行角色的权限授予才会真正生效。
三、为角色进行相应的授权
posted on 2012-04-11 11:25
@赵 阅读(1467)
评论(1) 编辑 收藏