zoukankan      html  css  js  c++  java
  • 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授权的理解了吧....

  • 相关阅读:
    【Android Developers Training】 73. 布局变化的动画
    【Android Developers Training】 72. 缩放一个视图
    【Android Developers Training】 71. 显示翻牌动画
    svn更改地址怎么办
    python学习手册
    failed to bind pixmap to texture
    Ubuntu 12.04安装Google Chrome
    svn update 时总是提示 Password for '默认密钥' GNOME keyring: 输入密码
    重设SVN 的GNOME keyring [(null)] 的密码
    Nginx + uWSGI + web.py 搭建示例
  • 原文地址:https://www.cnblogs.com/abcwt112/p/4586936.html
Copyright © 2011-2022 走看看