• Apache Shiro 学习记录4


      今天看了教程的第三章...是关于授权的......和以前一样.....自己也研究了下....我觉得看那篇教程怎么说呢.....总体上是为数不多的精品教程了吧....但是有些地方确实是讲的太少了....而这些地方又是蛮难的..比如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授权的理解了吧....

  • 相关阅读:
    小程序发展史
    ES6内置方法find 和 filter的区别在哪
    微信小程序开发踩坑记录
    小程序导航跳转一不小心踩进的坑
    谈谈如何对后台登陆界面进行渗透
    应急响应学习笔记
    php学习笔记
    代码审计学习笔记
    注入笔记(非sql注入)
    python安全编程学习
  • 原文地址:https://www.cnblogs.com/abcwt112/p/4586936.html
Copyright © 2020-2023  润新知