先说下需求场景吧;在开发基础门户框架权限管理时遇到这样一个问题:系统里面角色大概分为以下三类:1、超级管理员,2、普通管理员,3、应用角色;角色管理这个功能我会开放给以上三类用户;随之而来的问题是如果不做权限约束;那么任何角色下的用户都可以创建、修改、删除 角色列表里的数据;问题:

  • 可能会有破坏分子把超级管理员角色删除了;
  • 普通管理员可以修改自己的权限造成权限扩大;
  • 任何人都可以修改超级管理员权限造成权限丢失;
  • 等等
       这些都是系统中常见的问题;然而很多内部应用管理类系统都没有解决这个问题;或者采用硬编码的方式扩展性差;后期添加新角色后可能还要涉及到代码更改。

      因为门户框架使用了Shiro,那么我想让其帮我管理起来;熟悉Shiro框架的人都知道;Shiro 数据实例级别的权限约束通配符例如:role:update:1;如果用其自带的功能那么就要在用户登录的时候做好数据准备让用户可以修改的角色按照 role:update:1 这样的形式往SimpleAuthorizationInfo中添加,这里就牵涉出那些角色之间的权限级别。

      所谓权限级别就是:超级管理员可以随意的修改超级管理员、普通管理员、应用角色;

                                      普通管理员只能修改应用角色;不能篡改超级管理员的权限,也不能修改自己造成权限扩大,只能修改应用角色;

                                      应用角色权限最低只有查看各类角色基本信息的功能。

     所以权限范围是个躲不过去的问题;那么我就在Role类中添加了一个region属性来表示角色的权限级别,代码如下:


@Table(name="sys_role")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE,region="applicationBeanCache")
public class Role implements Serializable{
	private static final long serialVersionUID = -8136993944967274490L;
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Integer id;
	@Column(unique=true)
	private String name;
	@Column(name="description")
	private String desc;
	private Boolean canInherit; //是否可继承
	@ManyToMany(fetch=FetchType.LAZY)
	@JoinTable(name="sys_role_resource",joinColumns={@JoinColumn(name="role_id")},inverseJoinColumns={@JoinColumn(name="res_id")})
	@OrderBy(value="order")
	@Cache(usage=CacheConcurrencyStrategy.READ_WRITE,region="applicationBeanCache")
	private Set resources;
	private String region; //用来标识权限的范围
        ...

   Region 内置了三个类型:1、超级管理员(*)    2、普通管理员 (10)  3、应用角色 (20) 这里解释一下为什么这么设定:* 通配符表示可以修改所有的其他类别;这样超级管理管理员就可以修改其本身一级其他类别的角色了。普通角色和应用角色分别用10、20 这样方便后期的扩展如果以后增加了一类介于地市管理员和应用角色之间的角色那么久用11 ~ 19 比如会有组长、部门主管之类。

   有了权限范围;就可以扩展Shiro的Permission接口了;来实现自己的验证规则;这里我用的是role>*>*(可以修改所有角色包含自己)、role>*>10(可以修改类别比10大的)、role>*>20(修改类别比20大的;没有这类角色,及它谁都无法修改)这样的类型;既然我们实现自己的Permission那么我们就要让Shiro能解析出来;那么久要用到自定义权限通配符处理:PermissionResolver 通过实现这个接口来告知Shiro。


<!--

自定义Realm 继承自AuthorizingRealm

1、我们定义了一个portalWildPermissionResolver类实现PermissionResolver来具体负责role>*>*这类通配符的处理;代码稍后给出。

2、通过给Realm指定permissionResolver属性来指定自定义的通配符处理类

-->

<bean id="myRealm" class="cn.promore.bf.shiro.extend.MyRealm">

<property name="credentialsMatcher" ref="credentialsMatcher"/>

    <property name="cachingEnabled" value="true"/>  

    <property name="authenticationCachingEnabled" value="true"/>  

    <property name="authenticationCacheName" value="applicationBeanCache"/>

    <property name="permissionResolver" ref="portalWildPermissionResolver"/> 

</bean>

<bean id="portalWildPermissionResolver" class="cn.promore.bf.shiro.extend.PortalWildPermissionResolver"/>

  下面来看看cn.promore.bf.shiro.extend.PortalWildPermissionResolver都做了些什么;

/**

 *@author huzd@si-tech.com.cn or ahhzd@vip.qq

 *@version createrTime:Apr 24, 2017 5:04:45 PM

 * 自定义Shiro permission 权限处理类

 * 针对角色处理权限扩展一种形如 role>update>11 的权限字符串;

 * 最后一位为权限的处理范围标识;越小权限越大;* 为所有

 **/


public class PortalWildPermissionResolver implements PermissionResolver {

private static final Log logger = LogFactory.getLog(PortalWildPermissionResolver.class);

@Override

public Permission resolvePermission(String permissionString) {

                        //输出debug日志方便调试时查看

logger.debug("PortalWildPermissionResolver is invoke for ["+permissionString+"]");

//判断权限验证字符串时候为空;这里的PermissionString就是调用 SecurityUtils.getSubject().checkPermission("role>delete>"+tempRole.getRegion()); 时传递的参数;

                        //如果为空抛出权限非法异常;

if(StringUtils.isEmpty(permissionString)) throw new InvalidPermissionStringException("权限通配符不能为空",permissionString);

                        //用正则表达式来判断权限字符串是否符合role>update>11这类要求

                        //public static final String  ROLE_EXTEND_WILDCARD_PATTERN = "([A-Za-z0-9]*|\\*)>([A-Za-z0-9]*|\\*)>(\\d+|\\*)";

Pattern pattern = Pattern.compile(RoleWildCardPermission.ROLE_EXTEND_WILDCARD_PATTERN);

Matcher matcher = pattern.matcher(permissionString);

if(matcher.matches()){

                                //如果匹配那么创建一个自定义的Permission

return new RoleWildCardPermission(permissionString);

}

                        //如果不匹配那么用Shiro内置的权限例如:role:get、role:get:1 这种

        return new WildcardPermission(permissionString);  

}

}

      继续看RoleWildCardPermission类都做了些什么:



/**

 *@author huzd@si-tech.com.cn or ahhzd@vip.qq

 *@version createrTime:Apr 24, 2017 4:55:19 PM

 **/

public class RoleWildCardPermission implements Permission,Serializable{

               //如果使用了Cache来缓存权限;这里一定要实现Serializ able接口;否则会报错!

    private static final long serialVersionUID = -2072787372937622113L;

    private static final Log loger = LogFactory.getLog(RoleWildCardPermission.class);

    protected static final boolean DEFAULT_CASE_SENSITIVE = false;

    public static final String  ROLE_EXTEND_WILDCARD_PATTERN = "([A-Za-z0-9]*|\\*)>([A-Za-z0-9]*|\\*)>(\\d+|\\*)";

    private String resourceName;

    private String operate;

    private String authType;

 

    public RoleWildCardPermission(){super();}

    public RoleWildCardPermission(String permissionString){

    this(permissionString,DEFAULT_CASE_SENSITIVE);

    }

    public RoleWildCardPermission(String permissionString,boolean caseSensitive) {

    setParts(permissionString, caseSensitive);

    }  

/** 1、大写小处理,通配符是否区分大小写; 2、使用正则表达式来解析出 role > * > * 中的role、*、* 供后面判断使用。 ***/

    private void setParts(String permissionString,boolean caseSensitive){

    if(caseSensitive)permissionString.toLowerCase();

    Object[] array = new Object[3];

Pattern pattern = Pattern.compile(RoleWildCardPermission.ROLE_EXTEND_WILDCARD_PATTERN);

Matcher matcher = pattern.matcher(permissionString);

if(matcher.matches()&&matcher.groupCount()>0){

for (int i = 0; i < matcher.groupCount(); i++) {

array[i] = matcher.group(i+1);

}

resourceName = (String)array[0];

operate = (String)array[1];

authType =(String)array[2];

}

    }

/***

当我们调用 SecurityUtils.getSubject().checkPermission("role>delete>"+tempRole.getRegion()); 时最终都会到这个方法里面进行判断来告知shiro用户到底有没有对应的权限。 这个在Eclipse里面用断点就可以很轻松的找到Shiro的内部机制。 这里需要注意的是要把所有否的逻辑先罗列出来; **/   

    @Override

    public boolean implies(Permission p) {

        if(!(p instanceof RoleWildCardPermission))return false;

        RoleWildCardPermission other = (RoleWildCardPermission) p;

        boolean isImplies = true;

// !(所有为True的情况) 这句很好理解我就不说明了。

        if(!("*".equals(this.resourceName)||this.resourceName.equals(other.resourceName)))isImplies=false;

        if(!("*".equals(this.operate)||this.operate.equals(other.operate)))isImplies=false;

                //注意这里的<是有点讲究的;如果你先让管理员可以修改自己这种非通常情况这里就可以使用<=

        if(!("*".equals(this.authType)||

(Integer.valueOf("*".equals(this.authType)?"0":this.authType) <

Integer.valueOf("*".equals(other.authType)?"0":other.authType))))isImplies=false;

        loger.debug("------>校验用户权限结果:"+isImplies);

        return isImplies;

    }

}

 好了我们来总结一下:我们指定了解析器、实现了解析器、实现了自定义的Permission;那么我们调用SecurityUtils.getSubject().checkPermission("role>delete>"+tempRole.getRegion());的时候传递的权限和谁做比较的呢? 被比较的对象是在MyRealm初始化进去的;我们通常会把用户拥有的权限一次性查询出来;处理好放入MyReam中的SimpleAuthorizationInfo中;


//授权

@Override

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

String username = (String) super.getAvailablePrincipal(principals);

User user = userService.findByUsername(username);

if(null==user)return null;

List<Resource> resources =  resourceService.findRoleResources(user.getRoles(),null,ResourceService.RESOURCE_TYPE_FUNCTION);

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

if(null!=user.getRoles()&&user.getRoles().size()>0){

for(Role r:user.getRoles()){

info.addRole(r.getName());

}

                        //因为门户框架系统是多角色的;那么我们就要取出用户所有角色中权限范围最大的值;

//然后把权限范围传递给SimpleAuthorizationInfo 这样上面一个代码段中的this.resourceName 就是这里通过正则表达式解析出来的。

具体看用eclise Debug调试源代码得知。

String permission = "role>*>"+RoleUtils.getMaxRegion(user.getRoles());

info.addObjectPermission(new RoleWildCardPermission(permission));

}

if(null!=resources&&resources.size()>0){

for(Resource resource :resources){

info.addStringPermission(resource.getPath());

}

}

return info;

}

 有了以上准备我们就来实现了之前提到的需求:

  • 超级管理员可以随意的修改超级管理员、普通管理员、应用角色;
  • 普通管理员只能修改应用角色;不能篡改超级管理员的权限,也不能修改自己造成权限扩大,只能修改应用角色;
  • 应用角色权限最低只有查看各类角色基本信息的功能。
剩下就是我们具体Action里实现的逻辑了;这里我贴出部分以示范


public String add(){

SecurityUtils.getSubject().checkPermission("role:add");

try {

if(null!=role&&StringUtils.isNotEmpty(role.getRegion())){

SecurityUtils.getSubject().checkPermission("role>add>"+role.getRegion());

roleService.save(role);

flag = true;

message = "添加成功!";

}else{

flag = false;

message = "添加错误,关键参数缺失!";

}

MDC.put("operateContent","角色添加:"+role.toString()); 

LOG.info("");

} catch (AuthorizationException e) {

flag = false;

message = "不具备添加对应角色权限范围的权限";

} catch (Exception e) {

e.printStackTrace();

flag = false;

message = e.getMessage();

}

return "result";

}

//修改、删除 也以此类推。

系统效果图: