目录贴: 跟我学Shiro目录贴

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体

主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。

资源

在应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

权限

安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:

访问用户列表页面

查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)

打印文档等等。。。

如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro不会去做这件事情,而是由实现人员提供。

Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的),后续部分介绍。

角色

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

隐式角色:即直接通过角色来验证用户有没有操作权限,如在应用中CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。

显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。

请google搜索“RBAC”和“RBAC新解”分别了解“基于角色的访问控制”“基于资源的访问控制(Resource-Based Access Control)”。

3.1 授权方式

Shiro支持三种方式的授权:

编程式:通过写if/else授权代码块完成:

Java代码 收藏代码

Subject subject = SecurityUtils.getSubject(); 
 if(subject.hasRole(“admin”)) { 
 //有权限 
 } else { 
 //无权限 
 }

注解式:通过在执行的Java方法上放置相应的注解完成:

Java代码 收藏代码

@RequiresRoles(“admin”) 
 public void hello() { 
 //有权限 
 }


没有权限将抛出相应的异常;

JSP/GSP标签:在JSP/GSP页面通过相应的标签完成:

Java代码 收藏代码

自定义authorizer

authorizer=org.apache.shiro.authz.ModularRealmAuthorizer

自定义permissionResolver

permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver

permissionResolver=com.github.zhangkaitao.shiro.chapter3.permission.BitAndWildPermissionResolver 
 authorizer.permissionResolver=$permissionResolver

自定义rolePermissionResolver

rolePermissionResolver=com.github.zhangkaitao.shiro.chapter3.permission.MyRolePermissionResolver 
 authorizer.rolePermissionResolver=$rolePermissionResolver securityManager.authorizer=$authorizer


Java代码 收藏代码

自定义realm 一定要放在securityManager.authorizer赋值之后(因为调用setRealms会将realms设置给authorizer,并给各个Realm设置permissionResolver和rolePermissionResolver)

realm=com.github.zhangkaitao.shiro.chapter3.realm.MyRealm 
 securityManager.realms=$realm


设置securityManager 的realms一定要放到最后,因为在调用SecurityManager.setRealms时会将realms设置给authorizer,并为各个Realm设置permissionResolver和rolePermissionResolver。另外,不能使用IniSecurityManagerFactory创建的IniRealm,因为其初始化顺序的问题可能造成后续的初始化Permission造成影响。

2、定义BitAndWildPermissionResolver及BitPermission

BitPermission用于实现位移方式的权限,如规则是:

权限字符串格式:+资源字符串+权限位+实例ID;以+开头中间通过+分割;权限:0 表示所有权限;1 新增(二进制:0001)、2 修改(二进制:0010)、4 删除(二进制:0100)、8 查看(二进制:1000);如 +user+10 表示对资源user拥有修改/查看权限。

Java代码 收藏代码

public class BitPermission implements Permission { 
 private String resourceIdentify; 
 private int permissionBit; 
 private String instanceId; 
 public BitPermission(String permissionString) { 
 String[] array = permissionString.split(“\+”); 
 if(array.length > 1) { 
 resourceIdentify = array[1]; 
 } 
 if(StringUtils.isEmpty(resourceIdentify)) { 
 resourceIdentify = “*”; 
 } 
 if(array.length > 2) { 
 permissionBit = Integer.valueOf(array[2]); 
 } 
 if(array.length > 3) { 
 instanceId = array[3]; 
 } 
 if(StringUtils.isEmpty(instanceId)) { 
 instanceId = “*”; 
 } 
 }

@Override  
public boolean implies(Permission p) {  
    if(!(p instanceof BitPermission)) {  
        return false;  
    }  
    BitPermission other = (BitPermission) p;  
    if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))) {  
        return false;  
    }  
    if(!(this.permissionBit ==0 || (this.permissionBit & other.permissionBit) != 0)) {  
        return false;  
    }  
    if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))) {  
        return false;  
    }  
    return true;  
}


Permission接口提供了boolean implies(Permission p)方法用于判断权限匹配的;

Java代码 收藏代码

public class BitAndWildPermissionResolver implements PermissionResolver { 
 @Override 
 public Permission resolvePermission(String permissionString) { 
 if(permissionString.startsWith(“+”)) { 
 return new BitPermission(permissionString); 
 } 
 return new WildcardPermission(permissionString); 
 } 
 }


BitAndWildPermissionResolver实现了PermissionResolver接口,并根据权限字符串是否以“+”开头来解析权限字符串为BitPermission或WildcardPermission。

3、定义MyRolePermissionResolver

RolePermissionResolver用于根据角色字符串来解析得到权限集合。

Java代码 收藏代码

public class MyRolePermissionResolver implements RolePermissionResolver { 
 @Override 
 public Collection resolvePermissionsInRole(String roleString) { 
 if(“role1”.equals(roleString)) { 
 return Arrays.asList((Permission)new WildcardPermission(“menu:*”)); 
 } 
 return null; 
 } 
 }


此处的实现很简单,如果用户拥有role1,那么就返回一个“menu:*”的权限。

4、自定义Realm

Java代码 收藏代码

public class MyRealm extends AuthorizingRealm { 
 @Override 
 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 
 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 
 authorizationInfo.addRole(“role1”); 
 authorizationInfo.addRole(“role2”); 
 authorizationInfo.addObjectPermission(new BitPermission(“+user1+10”)); 
 authorizationInfo.addObjectPermission(new WildcardPermission(“user1:*”)); 
 authorizationInfo.addStringPermission(“+user2+10”); 
 authorizationInfo.addStringPermission(“user2:*”); 
 return authorizationInfo; 
 } 
 @Override 
 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 
 //和com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1. getAuthenticationInfo代码一样,省略 
 } 
 }


此时我们继承AuthorizingRealm而不是实现Realm接口;推荐使用AuthorizingRealm,因为:

AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):表示获取身份验证信息;

AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):表示根据用户身份获取授权信息。

这种方式的好处是当只需要身份验证时只需要获取身份验证信息而不需要获取授权信息。对于AuthenticationInfo和AuthorizationInfo请参考其Javadoc获取相关接口信息。

另外我们可以使用JdbcRealm,需要做的操作如下:

1、执行sql/ shiro-init-data.sql 插入相关的权限数据;

2、使用shiro-jdbc-authorizer.ini配置文件,需要设置jdbcRealm.permissionsLookupEnabled

为true来开启权限查询。

此次还要注意就是不能把我们自定义的如“+user1+10”配置到INI配置文件,即使有IniRealm完成,因为IniRealm在new完成后就会解析这些权限字符串,默认使用了WildcardPermissionResolver完成,即此处是一个设计权限,如果采用生命周期(如使用初始化方法)的方式进行加载就可以解决我们自定义permissionResolver的问题。

5、测试用例

Java代码 收藏代码
public class AuthorizerTest extends BaseTest {

@Test  
public void testIsPermitted() {  
    login("classpath:shiro-authorizer.ini", "zhang", "123");  
    //判断拥有权限:user:create  
    Assert.assertTrue(subject().isPermitted("user1:update"));  
    Assert.assertTrue(subject().isPermitted("user2:update"));  
    //通过二进制位的方式表示权限  
    Assert.assertTrue(subject().isPermitted("+user1+2"));//新增权限  
    Assert.assertTrue(subject().isPermitted("+user1+8"));//查看权限  
    Assert.assertTrue(subject().isPermitted("+user2+10"));//新增及查看  

    Assert.assertFalse(subject().isPermitted("+user1+4"));//没有删除权限  

    Assert.assertTrue(subject().isPermitted("menu:view"));//通过MyRolePermissionResolver解析得到的权限  
}

}
通过如上步骤可以实现自定义权限验证了。另外因为不支持hasAnyRole/isPermittedAny这种方式的授权,可以参考我的一篇《简单shiro扩展实现NOT、AND、OR权限验证 》进行简单的扩展完成这个需求,在这篇文章中通过重写AuthorizingRealm里的验证逻辑实现的。

示例源代码:https://github.com/zhangkaitao/shiro-example