Shiro中的授权问题(二)

上篇博客(Shiro中的授权问题 )我们介绍了Shiro中最最基本的授权问题,以及常见的权限字符的匹配问题。但是这里边还有许多细节需要我们继续介绍,本节我们就来看看Shiro中授权的一些细节问题。

验证流程

首先我们要搞明白整个的验证流程是什么样子的。 在上篇博客(Shiro中的授权问题 )中,我们验证Subject是否具备某项权限的时候使用的是isPermitted方法,但是在上上篇博客(初识Shiro )中,我们也说过,Subject只是Shiro中的一个门面而已,最终所有的操作都是委托给SecurityManager来处理的,那么这里也一样,我们调用了Subject的isPermitted方法,该方法会将验证操作委托给SecurityManager,SecurityManager又会将验证操作委托给Authorizer,Authorizer是我们这里真正的授权者,当我们调用isPermitted方法的时候,Authorizer首先会将我们传入的权限字符串转为相应的Permission实例,同时,系统也会调用Realm来获取Subject相应的角色或者权限,然后Authorizer会将我们传入的权限/角色和Realm中的权限/角色进行比对验证,而我们如果有多个Realm,则这个比对的操作又会被委托给ModularRealmAuthorizer进行循环判断,在判断的过程中,如果匹配成功就会返回true,否则返回false表示授权失败。

实例

OK,基于上文我们对授权过程的介绍,我们来自定义几个东西。验证一下我们上文的说法。在自定义之前,我们还是先来了解几个概念:Authorizer在Shiro中扮演的职责是授权,即访问控制,Authorizer提供了我们进行角色、权限判断时需要的接口等,我们常说的SecurityManager继承了Authorizer,还有一个类叫做PermissionResolver用于解析权限字符串到Permission实例,RolePermissionResolver则用于根据角色解析相应的权限集合,了解了这几个类的功能之后,我们来看看下面的自定义问题。 不知道小伙伴们对Linux中的权限机制是否有了解,Linux中用1、2、4三个数字分别表示一个文件的可读可写可执行三种权限,如果想要文件获取多个权限,将对应的数字相加即可。OK,那么我们这里就参考这种模式,我来定义一个权限模型: 假设我用0表示所有权限,1表示create权限,2表示update权限,4表示delete权限,8表示view权限,然后在实际应用中,我用数字之和来表示资源所具备的权限,比如3表示该资源既有create权限又有update权限,10表示该资源既有update权限又有view权限,然后权限的定义以$符号开始,资源和权限之间以$符号分隔。OK,基于此,我们来看看如果自定义权限处理机制。

自定义权限策略

自定义Permission类实现Permission接口:

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 (resourceIdentify == null || "".equals(resourceIdentify)) {
            resourceIdentify = "*";
        }
        if (array.length > 2) {
            permissionBit = Integer.valueOf(array[2]);
        }
        if (array.length > 3) {
            instanceId = array[3];
        }
        if (instanceId == null || "".equals(instanceId)) {
            instanceId = "*";
        }

    }

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

OK,这个是我们自定义的权限解析类,该类中有一个implies方法用来执行权限匹配操作,然后我们还需要提供了一个PermissionResolver类,该类将权限字符串解析为相应的类。如下:

public class BitAndWildPermissionResolver implements PermissionResolver {
    public Permission resolvePermission(String permissionString) {
        if (permissionString.startsWith("$")) {
            return new BitPermission(permissionString);
        }
        return new WildcardPermission(permissionString);
    }
}

我们在这里进行简单的判断,如果权限字符串是以$开始的,我们使用自定义的BitPermission进行解析,如果是其他普通的权限字符串,我们就创建WildcardPermission即可。然后我们自定义需要的Realm,在Realm中定义权限,如下:

public class MyRealm extends AuthorizingRealm {
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addObjectPermission(new BitPermission("$user1$14"));
        authorizationInfo.addStringPermission("$user2$10");
        return authorizationInfo;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        String password = new String(((char[]) token.getCredentials()));
        if (!"wang".equals(username)) {
            //用户名错误
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            //密码错误
            throw new IncorrectCredentialsException();
        }
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

这里我定义了两种权限,一个是$user1$14,表示了create(2),delete(4)和view(8)三种权限;还有一个是$user2$10,表示了create(2)和view(8)两种权限。然后我们来看看在ini文件中如何配置:

[main]
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver= org.sang.BitAndWildPermissionResolver
authorizer.permissionResolver=$permissionResolver
securityManager.authorizer=$authorizer
realm= org.sang.MyRealm
securityManager.realms=$realm

我们来看看测试代码:

    @Test
    public void test99() {
        login("classpath:shiro-custom.ini", "wang", "123");
        Subject subject = SecurityUtils.getSubject();
        Assert.assertTrue(subject.isPermitted("$user1$2"));//OK
        Assert.assertTrue(subject.isPermitted("$user1$8"));//OK
        Assert.assertTrue(subject.isPermitted("$user2$10"));//OK
        Assert.assertTrue(subject.isPermitted("$user1$4"));//OK
    }

测试结果如下: 绿色表示运行都是OK的。 OK,除了上面 这种自定义的形式之外,我们还可以自定义role解析,根据role的字符串解析出role中的权限集合。

自定义角色解析策略

我们也可以自己来设计角色解析策略,如下:

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

如果用户具有role1角色,那么我让他具有menu:*权限,OK,然后我在Realm中增加两个权限,新的Realm类如下:

public class MyRealm extends AuthorizingRealm {
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRole("role1");
        authorizationInfo.addRole("role2");
        authorizationInfo.addObjectPermission(new BitPermission("$user1$14"));
        authorizationInfo.addObjectPermission(new WildcardPermission("user1:*"));
        authorizationInfo.addStringPermission("$user2$10");
        authorizationInfo.addStringPermission("user2:*");
        return authorizationInfo;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        String password = new String(((char[]) token.getCredentials()));
        if (!"wang".equals(username)) {
            //用户名错误
            throw new UnknownAccountException();
        }
        if (!"123".equals(password)) {
            //密码错误
            throw new IncorrectCredentialsException();
        }
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

我让用户具备role1、role2角色,同时还为之添加了两个普通的权限、user1:*user2:*,这个时候我要稍微修改一个我的ini文件,如下:

[main]
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer
permissionResolver= org.sang.BitAndWildPermissionResolver
authorizer.permissionResolver=$permissionResolver
rolePermissionResolver= org.sang.MyRolePermissionResolver
authorizer.rolePermissionResolver=$rolePermissionResolver
securityManager.authorizer=$authorizer
realm= org.sang.MyRealm
securityManager.realms=$realm

新的测试代码如下:

 @Test
    public void test99() {
        login("classpath:shiro-custom.ini", "wang", "123");
        Subject subject = SecurityUtils.getSubject();
        Assert.assertTrue(subject.isPermitted("user1:update"));//OK
        Assert.assertTrue(subject.isPermitted("user2:update"));//OK
        Assert.assertTrue(subject.isPermitted("$user1$2"));//OK
        Assert.assertTrue(subject.isPermitted("$user1$8"));//OK
        Assert.assertTrue(subject.isPermitted("$user2$10"));//OK
        Assert.assertTrue(subject.isPermitted("$user1$4"));//OK
        Assert.assertTrue(subject.isPermitted("menu:view"));//OK
    }

测试结果如下:

OK,以上就是Shiro中自定义授权的问题。

本文案例 下载: https://github.com/lenve/Shiro/tree/master/Shiro2

更多JavaWeb资料,请移步这里: https://github.com/lenve/JavaEETest

参考资料: 张开涛大神的《跟我学Shiro》,原文连接http://jinnianshilongnian.iteye.com/blog/2018398

以上。

原文发布于微信公众号 - 玩转JavaEE(gh_d1ca11234a30)

原文发表时间:2017-03-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java架构沉思录

Java中如何提升锁性能

比如100个人去银行办理业务,要填一百张表,但是只有一支笔,那么很显然,每个人用笔的时间越短,效率也就越高。看代码:

832
来自专栏java 成神之路

URL 源码分析

37913
来自专栏移动开发

ButterKnife和Realm同时引用的小问题

772
来自专栏杨熹的专栏

Pandas常用命令-2

计数 s = pd.Series(np.random.randint(0, 7, size=10)) s.value_counts() ? 把数据拼接起来 df...

4306
来自专栏hbbliyong

依赖注入(IOC)二

上一章我们讲了构造注入与设值注入,这一篇我们主要讲接口注入与特性注入。 接口注入 接口注入是将抽象类型的入口以方法定义在一个接口中,如果客户类型需要获得这个...

3527
来自专栏LhWorld哥陪你聊算法

【Linux篇】--awk的使用

awk是一个强大的文本分析工具。相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大。 简单来说awk就是把文件逐行的读入,(空格...

1382
来自专栏计算机视觉与深度学习基础

Leetcode 37 Sudoku Solver 深搜基础题+位运算

Write a program to solve a Sudoku puzzle by filling the empty cells. Empty cel...

1877
来自专栏算法channel

玩转Pandas,让数据处理更easy系列3

前面介绍了Pandas最重要的两个类:Series和DataFrame,讲述了这两种数据结构常用的属性和操作,比如values,index, columns,索...

1001
来自专栏极客猴

Django 学习笔记之模型高级用法(上)

前面有两篇文章简单介绍 Django 的模型,这一部分算是基础知识。我自己近期也总做了下总结,将花大概两篇的篇幅来分享下模型的一些高级用法。

703
来自专栏Java学习网

Java阻塞队列线程集控制的实现方法

Java阻塞队列线程集控制的实现方法 队列以一种先进先出的方式管理数据。如果你试图向一个已经满了的阻塞队列中添加一个元素,或是从一个空的阻塞队列中移除一个元素...

2838

扫码关注云+社区

领取腾讯云代金券