今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了....而这些地方又是蛮难的..比如3.5节Authorizer、PermissionResolver及RolePermissionResolver...可能作者觉得讲清楚要花太多的篇幅涉及太多的类吧.....但是我看起来就很不爽0.0....既然提到了就想弄明白.....不然太纠结了....所以就有了这篇学习记录...记录我对Shiro授权的理解

Subject

我是从subject.isPermittedAll("system:view")这里开始研究的...

和我的上一篇学习记录写到的一样Subject只是一个接口,它的实现类是DelegatingSubject类.

  1. public boolean isPermittedAll(String... permissions) {
  2. return hasPrincipals() && securityManager.isPermittedAll(getPrincipals(), permissions);
  3. }

授权和认证太像啦,这里subject其实也是委托securityManager来具体处理的(第2行).

SecurityManager

securityManager的实现类是DefaultSecurityManager...DefaultSecurityManager的N层父类AuthorizingSecurityManager里有isPermitted方法

  1. public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
  2. return this.authorizer.isPermittedAll(principals, permissions);
  3. }

从中可以看出....Shiro的认证和授权真的是太像了...这里授权是委托授权器authorizer来做具体的授权,就像我学习认证那篇文章讲的,认证是委托authenticator来做一样.

ModularRealmAuthorizer

我想再吐槽一下.....ModularRealmAuthorizer和ModularRealmAuthenticator真是亲兄弟啊......我差点就看错名字了....

  1. public boolean isPermittedAll(PrincipalCollection principals, String... permissions) {
  2. assertRealmsConfigured();
  3. if (permissions != null && permissions.length > 0) {
  4. for (String perm : permissions) {
  5. if (!isPermitted(principals, perm)) {
  6. return false;
  7. }
  8. }
  9. }
  10. return true;
  11. }

这里就是把permissions一个一个取出来,调用isPermitted(principals, perm)方法去看看用户是否有每一个传入的权限perm(第5行)...........

全部包含就是返回true,否则返回false.

那我们就去看看isPermitted方法好了

  1. public boolean isPermitted(PrincipalCollection principals, String permission) {
  2. assertRealmsConfigured();
  3. for (Realm realm : getRealms()) {
  4. if (!(realm instanceof Authorizer)) continue;
  5. if (((Authorizer) realm).isPermitted(principals, permission)) {
  6. return true;
  7. }
  8. }
  9. return false;
  10. }

从这里(第5行)可以看出其实ModularRealmAuthorizer是调用了Realm的isPermitted的....

AuthorizingRealm

  1. public boolean isPermitted(PrincipalCollection principals, String permission) {
  2. Permission p = getPermissionResolver().resolvePermission(permission);
  3. return isPermitted(principals, p);
  4. }
  5.  
  6. public boolean isPermitted(PrincipalCollection principals, Permission permission) {
  7. AuthorizationInfo info = getAuthorizationInfo(principals);
  8. return isPermitted(permission, info);
  9. }
  10.  
  11. private boolean isPermitted(Permission permission, AuthorizationInfo info) {
  12. Collection<Permission> perms = getPermissions(info);
  13. if (perms != null && !perms.isEmpty()) {
  14. for (Permission perm : perms) {
  15. if (perm.implies(permission)) {
  16. return true;
  17. }
  18. }
  19. }
  20. return false;
  21. }

那么Realm又是怎么处理的呢?

在第一个方法中调用PermissionResolver去把要验证的字符串的permission转化成了实实在在的Permission对象...这是很重要的一步(但是PermissionResolver在第三个方法中也有用到,所以这里就不提了)....

然后再第二个方法中根据传入的principals调用Realm的getAuthorizationInfo(principals)得到AuthorizationInfo ....这里大家一定很熟悉...因为自己写Realm的话一定会去覆盖这个getAuthorizationInfo方法..返回的AuthenorizationInfo里可以得到用户的权限...只不过也是字符串形式......

然后就到了第三个方法....第12行,把info再转化成Permission对象的集合...然后遍历这个集合,去和传入的permission比较,看看用户的permission是否包含(implies)传入的permission....

  1. private Collection<Permission> getPermissions(AuthorizationInfo info) {
  2. Set<Permission> permissions = new HashSet<Permission>();
  3.  
  4. if (info != null) {
  5. Collection<Permission> perms = info.getObjectPermissions();
  6. if (!CollectionUtils.isEmpty(perms)) {
  7. permissions.addAll(perms);
  8. }
  9. perms = resolvePermissions(info.getStringPermissions());
  10. if (!CollectionUtils.isEmpty(perms)) {
  11. permissions.addAll(perms);
  12. }
  13.  
  14. perms = resolveRolePermissions(info.getRoles());
  15. if (!CollectionUtils.isEmpty(perms)) {
  16. permissions.addAll(perms);
  17. }
  18. }
  19.  
  20. if (permissions.isEmpty()) {
  21. return Collections.emptySet();
  22. } else {
  23. return Collections.unmodifiableSet(permissions);
  24. }
  25. }

AuthorizationInfo对象(包含用户字符串形式的权限)是如何提取出Permission对象的呢?

从上面的方法可以看出权限是来自3个方法和合并..info.getObjectPermissions().....resolvePermissions(info.getStringPermissions())和resolveRolePermissions(info.getRoles())....

第一个方法info.getObjectPermissions()我也不知道它是做啥的....看了源文档的注释还是不懂(%>_<%)......我Shiro也仅仅只写过一个demo.......实在是不了解......

第二个方法resolvePermissions(info.getStringPermissions())很重要..要提到WildcardPermissionResolver和WildcardPermission....等会再说....

第三个方法resolveRolePermissions(info.getRoles())我觉得是个预留的方法....要用到RolePermissionResolver接口的子类来做具体的处理....但是Shiro没有提供任何实现...大家可以去看看教程...开涛哥的教程里有他的实现.....我看了下源文档的注释....以我英语6级500都不到的渣渣功力来解读的话(=.=)大致意思就是说有些时候只能获取到subject的role,但是没有permission,这个时候用RolePermissionResolver可以根据role解读出permission...嗯....大致就是这么个意思...错了别打我....Σ( ° △ °|||)︴

然后重点自然就是第二个方法resolvePermissions(info.getStringPermissions())啦....

  1. private Collection<Permission> resolvePermissions(Collection<String> stringPerms) {
  2. Collection<Permission> perms = Collections.emptySet();
  3. PermissionResolver resolver = getPermissionResolver();
  4. if (resolver != null && !CollectionUtils.isEmpty(stringPerms)) {
  5. perms = new LinkedHashSet<Permission>(stringPerms.size());
  6. for (String strPermission : stringPerms) {
  7. Permission permission = getPermissionResolver().resolvePermission(strPermission);
  8. perms.add(permission);
  9. }
  10. }
  11. return perms;
  12. }

那么Shiro是如何把String的permission转化成实实在在的Permission对象的呢?

那就要用到PermissionResolver 的resolvePermission方法了(从上面第7行可以看出)...

WildcardPermissionResolver

WildcardPermissionResolver在Shiro里是PermissionResolver 的默认实现....

看看这个类名真是高大上...但是其实代码超级短....

  1. public class WildcardPermissionResolver implements PermissionResolver {
  2.  
  3. /**
  4. * Returns a new {@link WildcardPermission WildcardPermission} instance constructed based on the specified
  5. * <tt>permissionString</tt>.
  6. *
  7. * @param permissionString the permission string to convert to a {@link Permission Permission} instance.
  8. * @return a new {@link WildcardPermission WildcardPermission} instance constructed based on the specified
  9. * <tt>permissionString</tt>
  10. */
  11. public Permission resolvePermission(String permissionString) {
  12. return new WildcardPermission(permissionString);
  13. }
  14. }

就是返回new了一个WildcardPermission仅此而已....

WildcardPermission

  1. public WildcardPermission(String wildcardString) {
  2. this(wildcardString, DEFAULT_CASE_SENSITIVE);
  3. }
  4.  
  5. public WildcardPermission(String wildcardString, boolean caseSensitive) {
  6. setParts(wildcardString, caseSensitive);
  7. }

构造方法主要就是调用了setParts方法....另外稍微提一下....DEFAULT_CASE_SENSITIVE默认是false....那就是不区分字符串权限大小写咯~(system:view和System:vIew是一会事情)

  1. protected void setParts(String wildcardString, boolean caseSensitive) {
  2. if (wildcardString == null || wildcardString.trim().length() == 0) {
  3. throw new IllegalArgumentException("Wildcard string cannot be null or empty. Make sure permission strings are properly formatted.");
  4. }
  5.  
  6. wildcardString = wildcardString.trim();
  7.  
  8. List<String> parts = CollectionUtils.asList(wildcardString.split(PART_DIVIDER_TOKEN));
  9.  
  10. this.parts = new ArrayList<Set<String>>();
  11. for (String part : parts) {
  12. Set<String> subparts = CollectionUtils.asSet(part.split(SUBPART_DIVIDER_TOKEN));
  13. if (!caseSensitive) {
  14. subparts = lowercase(subparts);
  15. }
  16. if (subparts.isEmpty()) {
  17. throw new IllegalArgumentException("Wildcard string cannot contain parts with only dividers. Make sure permission strings are properly formatted.");
  18. }
  19. this.parts.add(subparts);
  20. }
  21.  
  22. if (this.parts.isEmpty()) {
  23. throw new IllegalArgumentException("Wildcard string cannot contain only dividers. Make sure permission strings are properly formatted.");
  24. }
  25. }

那么setParts方法做了些什么呢?

这个方法主要就是为WildcardPermission的成员域List<Set<String>> parts去赋值....我们来看看分隔符....

protected static final String WILDCARD_TOKEN = "*";
protected static final String PART_DIVIDER_TOKEN = ":";
protected static final String SUBPART_DIVIDER_TOKEN = ",";

然后大家再看代码应该就没什么问题了...这个代码我觉得没啥好说的...我就举几个例子吧...可能更直观点....

如果用户的字符串权限是"system:user:create,view"

那么parts这个成员域List的size是3.第一个set包含system...第二个set包含user....第三个set的size是2,包含create和view

构造方法和setParts就做了这些事情! 其实一点也不多....

好了...现在要检测的传入的字符串权限被转化成了Permission对象....用户拥有的字符串权限也转化成了Permission对象....那么如何判断用户是否有传入的权限呢?

在前面AuthorizingRealm那小节里我看看到了是调用perm.implies(permission)来判断是否包含的....

请注意..perm是用户的权限...permission是传入要检测的权限...

我们来看看implies方法吧....

  1. public boolean implies(Permission p) {
  2. // By default only supports comparisons with other WildcardPermissions
  3. if (!(p instanceof WildcardPermission)) {
  4. return false;
  5. }
  6.  
  7. WildcardPermission wp = (WildcardPermission) p;
  8.  
  9. List<Set<String>> otherParts = wp.getParts();
  10.  
  11. int i = 0;
  12. for (Set<String> otherPart : otherParts) {
  13. // If this permission has less parts than the other permission, everything after the number of parts contained
  14. // in this permission is automatically implied, so return true
  15. if (getParts().size() - 1 < i) {
  16. return true;
  17. } else {
  18. Set<String> part = getParts().get(i);
  19. if (!part.contains(WILDCARD_TOKEN) && !part.containsAll(otherPart)) {
  20. return false;
  21. }
  22. i++;
  23. }
  24. }
  25.  
  26. // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
  27. for (; i < getParts().size(); i++) {
  28. Set<String> part = getParts().get(i);
  29. if (!part.contains(WILDCARD_TOKEN)) {
  30. return false;
  31. }
  32. }
  33.  
  34. return true;
  35. }

代码就这么点...但是解释起来可能要很多篇幅....所以我还是觉得举例子比较快......其实这个方法就是比较2个Permission的parts部分....

比较是一部分一部分进行的...先List[0]和List[0]比较(第一个for的第一次循环)...再List[1]和List[1]比较(第一个for的第二次循环)....

比如用户权限是"*:user,admin:view" 传入要检测的权限是"system:guest:view"

第一个for的第一轮比较...看代码第19行....因为用户parts的[0]是*  !part.contains(WILDCARD_TOKEN)是false..所以继续下一轮for循环...意思就是*肯定包含system

然后进入for的第二轮!part.contains(WILDCARD_TOKEN)是true..所以要看!part.containsAll(otherPart)...

变量part是字符串user和admin的集合...otherPart是字符串guest的集合..!part.containsAll(otherPart)返回是true..所以if成立...所以return false..即用户是不包含传入检测的权限的(当然这里其实没有这么快就能得出这个结论..ModularRealmAuthorizer中如果有多个realm,要所有realm都返回false才能判断用户没有这个权限..因为程序可能会配置多个Realm...多个Realm可能会给一个用户取到不同的权限...所以每个Realm都要检测过来才知道用户到底包不包含传入要检测的权限...这个道理蛮简单的..大家都懂的..我就是稍微提醒一下..我只是假设我这里就配置了一个realm.....哈哈)...

看了这个implies方法的流程...我想大家也能明白为什么user:*是能匹配user:view:123而*:view不能匹配system:user:view的道理吧....

因为在用户权限长度m小于传入权限长度n的时候,implies方法的第一个for最多做m次就能知道返回是false还是true了..后面的n-m次比较是不会做的...

而用户权限长度m大于传入权限长度n的时候,先做implies方法的第一个for的n次比较,如果还是没结果...再做第2个for..检测用户权限patrs[n-m]到parts[m-1]的部分..这些部分只要有任意一个不是*那就说明用户不具有传入待检测的权限...

以上就是我今天对Shiro授权的理解了吧....

Apache Shiro 学习记录4的更多相关文章

  1. Apache Shiro 学习记录1

    最近几天在学习Apache Shiro......看了一些大神们的教程.....感觉收获不少.....但是毕竟教程也只是指引一下方向....即使是精品教程,仍然有很多东西都没有说明....所以自己也稍 ...

  2. Apache Shiro 学习记录5

    本来这篇文章是想写从Factory加载ini配置到生成securityManager的过程的....但是貌似涉及的东西有点多...我学的又比较慢...很多类都来不及研究,我又怕等我后面的研究了前面的都 ...

  3. Apache Shiro 学习记录2

    写完上篇随笔以后(链接).....我也想自己尝试一下写一个Strategy.....Shiro自带了3个Strategy,教程(链接)里作者也给了2个.....我想写个都不一样的策略.....看来看去 ...

  4. Apache Shiro 学习记录3

    晚上看了教程的第三章....感觉Shiro字符串权限很好用....但是教程举的例子太少了.....而且有些地方讲的不是很清楚....所以我也自己测试了一下....记录一下测试的结果.... (1) * ...

  5. Apache Shiro学习-2-Apache Shiro Web Support

     Apache Shiro Web Support  1. 配置 将 Shiro 整合到 Web 应用中的最简单方式是在 web.xml 的 Servlet ContextListener 和 Fil ...

  6. apache shiro学习笔记

    一.权限概述 1.1 认证与授权 认证:系统提供的用于识别用户身份的功能,通常登录功能就是认证功能-----让系统知道你是谁?? 授权:系统授予用户可以访问哪些功能的许可(证书)----让系统知道你能 ...

  7. Apache shiro学习总结

    Apache shiro集群实现 (一) shiro入门介绍 Apache shiro集群实现 (二) shiro 的INI配置 Apache shiro集群实现 (三)shiro身份认证(Shiro ...

  8. Java安全框架 Apache Shiro学习-1-ini 配置

    简单登录流程: 1.  SecurityManager   2.  SecurityUtils.setSecurityManager 3.  SecurityUtils.getSubject     ...

  9. shiro学习记录(三)

    1.使用ehcache缓存权限数据 ehcache是专门缓存插件,可以缓存Java对象,提高系统性能. l ehcache提供的jar包: 第一步:在pom.xml文件中引入ehcache的依赖 &l ...

随机推荐

  1. [Erlang 0120] Know a little Core Erlang

      Erlang开发者或多或少都用过或者听说过Core erlang,它是什么样的呢?新建一个测试模块a.erl,如下操作会生成core erlang代码而非a.beam:   Eshell V6.0 ...

  2. Mysql的Haproxy反向代理和负载均衡

    HaProxy提供高可用性.负载均衡以及基于TCP和HTTP应用的代理,支持虚拟主机,它是免费.快速并且可靠的一种解决方案.应用到Haproxy主要是因为他免费,并且基于TCP和HTTP的应用代理. ...

  3. Linux 系统中的MySQL数据库默认区分大小写

    今天在开发中遇到这么个问题,将连接的数据库改为服务器上的时候(服务器是Linux系统的),程序跑起来后一直出错,总提示数据库的表找不到, 而打开数据库看该表明明是存在的,在我的印象中MySQL数据是不 ...

  4. java在类定义时对hashset的便捷初始化方法

    有时候我们在类成员定义时,当这个类成员类型为 HashSet时,我们可以不方便调用 add函数进行初始化,所以可以采用下面的便捷方式来进行初始化 public class MyTest{ final ...

  5. DOS下windows系统查看wifi密码

    DOS下windows系统查看wifi密码 首先,按win+R键,win键如下 弹出框中输入cmd 在弹出界面输入 netsh wlan show profiles 你可以看到你链接过的所有wifi名 ...

  6. Crimm Imageshop 2.3。

    下载地址:http://files.cnblogs.com/Imageshop/ImageShop.rar 一款体积小,能绿色执行,又功能丰富的图像处理软件. Imageshop2.3为单EXE文件, ...

  7. 【原】移动web滑屏框架分享

    本月26号参加webrebuild深圳站,会上听了彪叔的对初心的讲解,“工匠精神”这个词又一次被提出,也再次引起了我对它的思考.专注一个项目并把它做得好,很好,更好...现实工作中,忙忙碌碌,抱着完成 ...

  8. Appium+python的一个简单完整的用例

    最近一直在忙,终于有时间来整理一下,传一个简单的用例,运行之后可以看到用例的报告,希望对大家有帮助. HTMLTestRunner这个包网上有很多,大家可以自己下载. 1 import unittes ...

  9. [LeetCode] Strobogrammatic Number II 对称数之二

    A strobogrammatic number is a number that looks the same when rotated 180 degrees (looked at upside ...

  10. [LeetCode] Combine Two Tables 联合两表

    Table: Person +-------------+---------+ | Column Name | Type | +-------------+---------+ | PersonId ...