Shiro Authorizer、PermissionResolver及RolePermissionResolver

JAVA herman 1523浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

限于篇幅的原因,我将Shiro 的授权这一部分分开了。《Shiro 授权 checkPermissions》讲的是代码和使用。这一章我们从Shiro的架构和原理讲Shiro 的授权。

Shiro 的授权流程

Shiro 授权流程

流程如下:

  1. 首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
  2. Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
  3. 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
  4. Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

ModularRealmAuthorizer进行多Realm匹配流程:

  1. 首先检查相应的Realm是否实现了实现了Authorizer;
  2. 如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
  3. 如果有一个Realm匹配那么将返回true,否则返回false。

如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:

  1. 如果调用hasRole*,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可;
    首先如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
  2. 通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色,并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);
  3. 接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false。

Authorizer

Authorizer的职责是进行授权(访问控制),是Shiro API中授权核心的入口点,其提供了相应的角色/权限判断接口,具体请参考其Javadoc。SecurityManager继承了Authorizer接口,且提供了ModularRealmAuthorizer用于多Realm时的授权匹配。PermissionResolver用于解析权限字符串到Permission实例,而RolePermissionResolver用于根据角色解析相应的权限集合。

通过如下ini配置更改Authorizer实现:

authorizer=org.apache.shiro.authz.ModularRealmAuthorizer  
securityManager.authorizer=$authorizer

对于ModularRealmAuthorizer,相应的AuthorizingSecurityManager会在初始化完成后自动将相应的realm设置进去,我们也可以通过调用其setRealms()方法进行设置。对于实现自己的authorizer可以参考ModularRealmAuthorizer实现。

设置ModularRealmAuthorizer的permissionResolver,其会自动设置到相应的Realm上(其实现了PermissionResolverAware接口),如:

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

设置ModularRealmAuthorizer的rolePermissionResolver,其会自动设置到相应的Realm上(其实现了RolePermissionResolverAware接口),如:

rolePermissionResolver=com.xttblog.permission.MyRolePermissionResolver  
authorizer.rolePermissionResolver=$rolePermissionResolver 

下面从代码的角度来说明,先看shiro-authorizer.ini配置:

[main]
#自定义authorizer
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer  
#自定义permissionResolver
#permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver  
permissionResolver=com.xttblog.permission.BitAndWildPermissionResolver  
authorizer.permissionResolver=$permissionResolver  
#自定义rolePermissionResolver
rolePermissionResolver=com.xttblog.permission.MyRolePermissionResolver  
authorizer.rolePermissionResolver=$rolePermissionResolver  

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

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

接下来我们需要定义BitAndWildPermissionResolver及BitPermission。BitPermission用于实现位移方式的权限,规则如下:

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

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)方法用于判断权限匹配的

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。

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

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

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

自定义Realm

public class XttblogRealm 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.xttblog.realm.XttblogRealm.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的问题。

测试用例代码如下:

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里的验证逻辑实现的。

相关代码下载链接:http://pan.baidu.com/s/1pKPAR1L 密码:kbj8

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » Shiro Authorizer、PermissionResolver及RolePermissionResolver