论坛首页 Java企业应用论坛

spring security 深度定制:自定义角色继承的扩展实现

浏览 7678 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2012-10-09  

写在前面:
发表之前,特意google了一下,没有找到类似主题,因此,请尊重一下作者的劳动成果,如需转载,请注明作者。

 

spring security 框架的核心是通过建立用户->角色->资源三者的多对多关系来管理用户权限,它的配置非常灵活,但正是这种灵活性给框架的使用者带来了诸多困惑,倒底是该给单一用户分配更多角色还是该把更多资源分配给某个特定角色呢,没有一定之规,我的做法是给单一用户分配单一角色,单一角色分配单一(种)资源,将权限模型简化为三者的近似一对一关系,然后利用角色继承,构造角色的层级关系,从而实现用户权限的扩展,这样一来,表记录将可以大大简化,维护也更为简便。用户登录上来,表现层上只有他的原始身份,不会有类似[ROLE_ADMIN,ROLE_USER]这样的复合身份,更加符合现实世界的需求。不过,虽然框架本身支持角色继承,但其实现方式略显怪异,需要在XML中以类似下面的格式来配置:

 

<beans:bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
	<beans:property name="hierarchy">
		<beans:value>
			ROLE_BOSS > ROLE_ADMIN
			ROLE_ADMIN > ROLE_SYSTEM_MANAGER
			ROLE_SYSTEM_MANAGER > ROLE_DISTRICT_1_MANAGER
			ROLE_SYSTEM_MANAGER > ROLE_DISTRICT_2_MANAGER
			ROLE_DISTRICT_1_MANAGER > ROLE_USER
			ROLE_DISTRICT_2_MANAGER > ROLE_USER
		</beans:value>
	</beans:property>
</beans:bean>

 

RoleHierarchyImpl实例对象中有一个方法getReachableGrantedAuthorities,读取这段文本,装配成权限,叠加进原始权限,供RoleVoter对象调用。在2.x中还可以使用数据库表来配置,但其用到的表结构也相当简陋,一个包含字段,一个被包含字段,看起来跟上面这段配置没有两样,这样的表在实际应用中几乎没有什么用,还不如上面这段配置来得简便。

 

仔细看这段配置,ROLE_SYSTEM_MANAGER继承了ROLE_DISTRICT_1_MANAGER的权限,同时它还继承了ROLE_DISTRICT_2_MANAGER的权限,这需要配置两条,而ROLE_DISTRICT_1_MANAGER和ROLE_DISTRICT_2_MANAGER位于同一级别,都继承了ROLE_USER的权限,还得配置两条,因此,一旦同级角色的数量增加,配置的条目数量将成倍增加,这实在不是一件令人高兴的事。况且,将继承关系写死在配置文件中,失去了灵活性,无法对继承关系进行动态管理,同样让人颇感不爽。

 

看来需要扩展框架的RoleHierarchy实现,初步想法很自然的第一步,就是在数据库的ROLE表中增加表示层级关系的LEVEL字段,让拥有高权限的角色包含低权限角色,然而这还不够,要体现灵活性,还要增加一个表示可继承与否的INHERITABLE字段,对继承关系进行约束,修改后的ROLE表结构是这样的:

 

Field Type Null Key Default Extra
id int(11) NO PRI NULL auto_increment
description varchar(255) YES   NULL  
name varchar(255) YES   NULL  
inheritable bit(1) YES NULL
level int(11) YES NULL  

 

接下来要颇费一番周折,RoleHierarchyImpl实现了RoleHierarchy接口,这个接口里只有一个方法getReachableGrantedAuthorities,需要把原始权限与ROLE表中的LEVEL关系进行比对,然后把符合条件的权限装配进去,麻烦就在于比对逻辑,为了提升效率,我先后写了两个实现,下面是最终的实现代码:

 

package com.hony.prj.security;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.GrantedAuthorityImpl;
import org.springframework.stereotype.Service;

@Service("roleHierarchy")
public class MyRoleHierarchyImpl implements RoleHierarchy {
	
	@Resource
	private HibernateTemplate hibernateTemplate;
	
	private List<GrantedAuthority> appAuthorities;
	
	private List<Boolean> appInheritables;
	
	private List<Integer> appLevels;

	@PostConstruct
	public void loadAppRoleAttrs() {
		List<Object[]> appRoleAttrs=hibernateTemplate.find(
				"select r.name,r.inheritable,r.level from Role r order by r.level asc");
		if(appRoleAttrs!=null){
			appAuthorities=new ArrayList<GrantedAuthority>();
			appInheritables=new ArrayList<Boolean>();
			appLevels=new ArrayList<Integer>();
			for(Object[] ra : appRoleAttrs){
				appAuthorities.add(new GrantedAuthorityImpl((String)ra[0]));
				appInheritables.add((Boolean)ra[1]);
				appLevels.add((Integer) ra[2]);
			}
		}
	}
	
	@Override
	public Collection<GrantedAuthority> getReachableGrantedAuthorities(
			Collection<GrantedAuthority> authorities) {
		if (authorities == null || authorities.isEmpty()) {
            			return AuthorityUtils.NO_AUTHORITIES;
        		}

		List<GrantedAuthority> reachableGrantedAuthorities=new ArrayList<GrantedAuthority>();
        		List<GrantedAuthority> currentAuthorities = new ArrayList<GrantedAuthority>();
        		currentAuthorities.addAll(authorities);
        		reachableGrantedAuthorities.addAll(currentAuthorities);

        		if(currentAuthorities.get(0).getAuthority().equals("ROLE_ANONYMOUS")){
    			return reachableGrantedAuthorities;
    		}
        		int aSize=appAuthorities.size();
        		int cSize=currentAuthorities.size();
        		String appCurrAuthName=null;
    		String currAuthName=null;
    		int i=0,j=0;
    		for(;i<aSize;i++){
    			appCurrAuthName=appAuthorities.get(i).getAuthority();
    			currAuthName=currentAuthorities.get(0).getAuthority();
    			if(appCurrAuthName.equals(currAuthName)){
    				break;
    			}
    		}
    		if(i==aSize-1){
			return reachableGrantedAuthorities;
		}
    		int currLevel=appLevels.get(i);
    		for(i++,j++;i<aSize;i++){
    			appCurrAuthName=appAuthorities.get(i).getAuthority();
    			boolean appCurrInheritable=appInheritables.get(i);
        			currAuthName=(j<cSize) ? currentAuthorities.get(j).getAuthority() : null;
			if(appCurrInheritable){
        				if(appCurrAuthName.equals(currAuthName)){
        					j++;
        				}
        				else{
        					if(currLevel!=appLevels.get(i)){
        						reachableGrantedAuthorities.add(appAuthorities.get(i));
        					}        			
        				}  
        			}
    		}
		for(GrantedAuthority authority : reachableGrantedAuthorities){
			System.out.println("reachableGrantedAuthority is: "+authority.getAuthority());
		}
		return reachableGrantedAuthorities;
	}
	
}

 

由于采用Annotation方式注入Bean对象,配置文件applicationContext-security.xml只需要简单地配置一下:

 

<beans:bean id="roleHierarchyVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
	<beans:constructor-arg ref="roleHierarchy" />
</beans:bean>

<beans:bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
	<beans:property name="allowIfAllAbstainDecisions" value="false"/>
	<beans:property name="decisionVoters">
        		<beans:list>
			<beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
			<beans:ref bean="roleHierarchyVoter" />
			<beans:bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
		</beans:list>
	</beans:property>
</beans:bean>

 

解释一下代码:

 

	@PostConstruct
	public void loadAppRoleAttrs() {
		List<Object[]> appRoleAttrs=hibernateTemplate.find(
				"select r.name,r.inheritable,r.level from Role r order by r.level asc");
		if(appRoleAttrs!=null){
			appAuthorities=new ArrayList<GrantedAuthority>();
			appInheritables=new ArrayList<Boolean>();
			appLevels=new ArrayList<Integer>();
			for(Object[] ra : appRoleAttrs){
				appAuthorities.add(new GrantedAuthorityImpl((String)ra[0]));
				appInheritables.add((Boolean)ra[1]);
				appLevels.add((Integer) ra[2]);
			}
		}
	}

 

Bean初始化时加载进Role权限,给三个成员变量赋值,这样只需加载一次即可,以后权限数据更新后也可以调这个方法更新实例中的成员变量,用到了PostConstruct注解。 

 

	if(currentAuthorities.get(0).getAuthority().equals("ROLE_ANONYMOUS")){
    		return reachableGrantedAuthorities;
    	}

 

如果配置文件中有类似于<intercept-url pattern="/index.jsp" access="IS_AUTHENTICATED_ANONYMOUSLY"/>这样的条目,需要特别处理权限为“ROLE_ANONYMOUS”的情况。

 

	for(;i<aSize;i++){
    		appCurrAuthName=appAuthorities.get(i).getAuthority();
    		currAuthName=currentAuthorities.get(0).getAuthority();
    		if(appCurrAuthName.equals(currAuthName)){
    			break;
    		}
    	}
    	if(i==aSize-1){
		return reachableGrantedAuthorities;
	}

 

查找现有权限在所有权限中起始位置(按LEVEL排序后),如果排在最后,就不用继续了。

 

	int currLevel=appLevels.get(i);
	for(i++,j++;i<aSize;i++){
    		appCurrAuthName=appAuthorities.get(i).getAuthority();
    		boolean appCurrInheritable=appInheritables.get(i);
        		currAuthName=(j<cSize) ? currentAuthorities.get(j).getAuthority() : null;
		if(appCurrInheritable){
        			if(appCurrAuthName.equals(currAuthName)){
        				j++;
        			}
        			else{
        				if(currLevel!=appLevels.get(i)){
        					reachableGrantedAuthorities.add(appAuthorities.get(i));
        				}        			
        			}  
        		}
    	} 

 

找到起始位置后,要把它的LEVEL值取出来,以避免同级包含。

其他要注意的是,我定义的排序逻辑是按升序排列,LEVEL值越小,权限越大,USER表对应的POJO类也要作适当修改:

 

User.class

	@Override
	public Collection<GrantedAuthority> getAuthorities() {
		List<GrantedAuthority> grantedAuthorities=new ArrayList<GrantedAuthority>(roles.size());
		Collections.sort(roles, new RoleByLevelComparator(SortType.ASC));
		for (Role role : roles){
			grantedAuthorities.add(new GrantedAuthorityImpl(role.getName()));
		}			
		return grantedAuthorities;
	}

 

Collections.sort(roles, new RoleByLevelComparator(SortType.ASC))确保在返回权限集合前按Level属性升序排列,虽然我自己用单一角色方案,但并不排斥框架允许的多角色方案,事先排序是完全有必要的。


大体上就是这些,如有疑问,欢迎交流。 

   发表时间:2012-10-09  
权限问题,我想有更高的要求:
1、企业应用包含了大量的商业机密同时需要复杂的分级权限系统,如何在高并发以及复杂的业务逻辑计算的大型ERP系统中的情况下具备高效的权限验证?是否能考虑业务系统和权限验证系统的分离?

2、权限的划分怎么让用户来自定义,让用户来自由组合、拆分,过于复杂的权限系统将需要非常专业的人员才能维护,如何降低系统维护的门槛?


下班了,暂时想到2个问题
0 请登录后投票
   发表时间:2012-10-09  
我先仔细研究一下楼主的成果
0 请登录后投票
   发表时间:2012-10-09  
这个有用,我一直在考虑,就是懒得动手,楼主既然提出了思路和做法,我们就顺手一起捉摸捉摸
0 请登录后投票
   发表时间:2012-10-09  
你这样是不是描述为一个线性继承? 权限继承应该是一个树形结构比较合理。
0 请登录后投票
   发表时间:2012-10-09  
vision2000 写道
权限问题,我想有更高的要求:
1、企业应用包含了大量的商业机密同时需要复杂的分级权限系统,如何在高并发以及复杂的业务逻辑计算的大型ERP系统中的情况下具备高效的权限验证?是否能考虑业务系统和权限验证系统的分离?

2、权限的划分怎么让用户来自定义,让用户来自由组合、拆分,过于复杂的权限系统将需要非常专业的人员才能维护,如何降低系统维护的门槛?


下班了,暂时想到2个问题

 

 这些与本主题涉及的内容基本无关,唯一的相关性是简化权限设计的目的就是为了降低系统维护的成本。

0 请登录后投票
   发表时间:2012-10-09  
melin 写道
你这样是不是描述为一个线性继承? 权限继承应该是一个树形结构比较合理。

你的理解有误,粗略看了一下关于RBAC的描述,我这个实现基本上算是RBAC3统一权限模型。
0 请登录后投票
   发表时间:2012-11-01  
如果角色间的权限是交叉存在的怎么办
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics