zoukankan      html  css  js  c++  java
  • Shiro

    Authorization

    说说权限的一些东东,不是Authentication,是Authorization。

    简单说就是access control即访问控制,控制用户对某个资源的访问。
    比如说,是否可以查看某个页面、修改某个数据,甚至能不能看到某个按钮。

    我们通常用三种元素进行授权操作,分别是:

    • Permissions: 这个在Shiro中代表粒度(granularity)最小的(原子性)的安全策略。
      权限的粒度也可以再细化三个等级:

      • 资源:比如我可以对用户信息进行修改
      • 实例:我可以对用户A的信息进行修改
      • 属性:我可以对用户A的信息中的姓名进行修改

      通常情况下,资源都支持CRUD操作。但是,每一种操作对一个资源有着不同的意义。
      所以我们尽力将权限的粒度做的小一些。
      Permission仅仅声明对某个资源可以进行什么样的操作。
      比如:能否看到“删除用户”按钮?能否浏览用户列表页面?

    • Roles: Role可以说是一系列动作的集合,通常将Role分配给User,User有没有操作权限归因于Role。
      Role有两种类型,分别是隐式(implicity)和显示(explicity)。

      • Implicity Roles:根据某个角色判断是否对资源有操作权限,粒度较粗。
      • Explicity Roles:关注是否有进行该操作的权限,角色只是聚合了权限,用户拥有某角色。 也可以理解为基于角色的访问权限控制与基于资源的访问权限控制(总之我讨厌这种咬文嚼字的感觉,但我们又有必要有效地表达出我们的想法)。
        一般更建议使用基于资源的访问权限控制。
    • Users: 即操作的主体,和Subject是一样的概念。
      可以根据角色或者权限决定是否允许用户执行某个操作。
      当然,我们可以直接将权限分配给用户,也可以将权限分配给角色再将角色分配给用户。
      或者我们也可以根据具体的需求再加一个层级。


    权限判断主要有3方式,分别是:

    • 编码方式:
      先说说基于角色的控制,相对基于资源的控制来得简单些。 关键是最后两行代码:

      Subject currentUser = SecurityUtils.getSubject();
      UsernamePasswordToken token = new UsernamePasswordToken("King","t;stmdtkg");
      currentUser.login(token);
      
      currentUser.hasRole("admin");
      currentUser.checkRole("admin");
      //用角色判断主要是两类方法,hasRole*和checkRole*,前者返回boolean,后者抛出异常。
      boolean hasRole(String roleIdentifier);
      boolean[] hasRoles(List<String> roleIdentifiers);
      boolean hasAllRoles(Collection<String> roleIdentifiers);
      
      void checkRole(String roleIdentifier) throws AuthorizationException;
      void checkRoles(Collection<String> roleIdentifiers) throws AuthorizationException;
      void checkRoles(String... roleIdentifiers) throws AuthorizationException;
      

    通常,基于资源的控制较基于角色的控制更加有效。
    用资源判断也是两类方法,isPermitted和checkPermission,前者返回boolean,后者抛出异常。
    但与基于角色的方法不同的是,基于资源的方法的参数除了String也可以是org.apache.shiro.authz.Permission类型。
    Permission是一接口,自定义一个Permission只需要实现boolean implies(Permission p)。
    如果想更准确地表达一个权限,或者想在权限执行时增加一些逻辑或访问一些资源则可以用Permission对象。

    • 注解方式:
      在方法上面加上权限注解。

      @RequiresRoles({"admin","leader"})
      public void deleteUsers(){
          //...
      }
      

      解释一下这五个annotation:

      • @RequiresAuthentication:访问或者调用被注解的类或者方法时通过认证。
      • @RequiresGuest:需要从未通过认证且没有被记住(Remember me)。
      • @RequiresPremissions:需要特定的权限。
      • @RequiresRoles:需要特定的角色。
      • @RequiresUser:需要已通过认证
    • 页面标签: 我们可以根据权限去影响页面的显示

      <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
      
      <shiro:hasPermission name="user!delete.do">
          <a href="###">删除用户</a>
      </shiro:hasPermission>
      

    Authorization Sequence


    图转自Shiro官网,但是这个步骤是在是太罗嗦了。

    Step 1.

    Subject实例(一般为DelegatingSubject)的权限验证方法被调用(也就是hasRole,checkRole,isPermitted,checkPermission这一系列)。
    DelegatingSubject:

    public boolean isPermitted(String permission) {
        return hasPrincipals() && securityManager.isPermitted(getPrincipals(), permission);
    }
    

     

    Step 2.

    获取所有的principals并将权限验证的工作委托给securityManager。 AuthorizingSecurityManager:

    public boolean isPermitted(PrincipalCollection principals, String permissionString) {
        return this.authorizer.isPermitted(principals, permissionString);
    }
    

     

    Step 3.

    securityManager直接将工作委托给其内部的authorizer。

    public AuthorizingSecurityManager() {
        super();
        this.authorizer = new ModularRealmAuthorizer();
    }
    

    默认为ModularRealmAuthorizer,ModularRealmAuthorizer支持与多个Realm进行交互。
    ModularRealmAuthorizer循环检查每一个Realm是否实现了Authorizer,检查通过则调用该Realm的权限验证方法。
    ModularRealmAuthorizer:

    public boolean isPermitted(PrincipalCollection principals, String permission) {
        assertRealmsConfigured();
        for (Realm realm : getRealms()) {
            if (!(realm instanceof Authorizer)) continue;
            if (((Authorizer) realm).isPermitted(principals, permission)) {
                return true;
            }
        }
        return false;
    }
    

     

    Step 4.

    Realm的权限验证方法被调用,从权限信息中获取权限集合,循环调用其implies方法判断是否拥有权限。

    (如图,部分Realm也实现了Authorizer。

    另外AuthorizingRealm的constructor中

    this.permissionResolver = new WildcardPermissionResolver();  
    

    如果传入的权限是以String形式表示,则需要一个resolvePermission的过程。
    此处会用到PermissionResolver将字符串转为Permission实例。
    如果Realm的权限验证方法出现异常,异常将作为AuthorizationException传至Subject的caller。
    而随后的Realm的验证方法都不会得到执行。
    如果Realm的权限验证方法返回boolean(比如hasRole或者isPermitted)并且其中一个返回true,剩余的Realm则全部短路。

    public boolean isPermitted(PrincipalCollection principals, String permission) {
        Permission p = getPermissionResolver().resolvePermission(permission);
        return isPermitted(principals, p);
    }
    
    public boolean isPermitted(PrincipalCollection principals, Permission permission) {
        AuthorizationInfo info = getAuthorizationInfo(principals);
        return isPermitted(permission, info);
    }
    
    private boolean isPermitted(Permission permission, AuthorizationInfo info) {
        Collection<Permission> perms = getPermissions(info);
        if (perms != null && !perms.isEmpty()) {
            for (Permission perm : perms) {
                if (perm.implies(permission)) {
                    return true;
                }
            }
        }
        return false;
    }
    

    PermissionResolver

    顺便说说这个PermissionResolver,主要用于将以String表示的权限转为Permission实例。
    如果想定义一个PermissionResolver,我们只需要实现一个方法。

    public interface PermissionResolver {
    /** * Resolves a Permission based on the given String representation. * * @param permissionString the String representation of a permission. * @return A Permission object that can be used internally to determine a subject's permissions. * @throws InvalidPermissionStringException * if the permission string is not valid for this resolver. */ Permission resolvePermission(String permissionString); }

    上面说过AuthorizingRealm(注意他下面还跟着一大票Realm)中默认使用的PermissionResolver实例为WildcardPermissionResolver。
    什么是WildcardPemission?

    用String表述权限的时候,即使我用"看用户列表页"、"晚上跑楼梯"、"open a file"这种字符串来描述也是没有问题的。
    但是他没有可以利用的规则,我们无法用某种规则去解释他(当然,有些情况下可能不需要解释)。
    Shiro提供了更直观有力的表述语法——WildcardPermission。

    比如我对某个资源有某些操作权限。
    举个栗子,对用户有查看权限

    user:query
    

    不仅有查看权限,还有增加、修改和删除

    user:query
    user:edit
    user:create
    user:delete
    

    也可以写成

    "user:query,edit,create,delete"
    

    如果对某个资源有所有操作权限,则:

    user:*
    

    或者也可以对所有资源拥有查看权限:

    *:query
    

    如果要表示仅对某资源的某实例有某权限,则

    user:query:king
    

    当然,"*"也适用于实例级别的权限

    user:*:king
    

    继续说说PermissionResolver。
    当以String表述权限时,多数AuthorizingRealm的实现都会先将其转换为Permission实例后再进行权限检查逻辑。
    权限检查并不是单纯的字符串比较。基于Permission对象的权限检查可以呈现更好的逻辑,比如wildcardPermission中如果包含"*"什么的就不是字符串比较那么简单了。
    因此,几乎所有的Realm都需要将String转为Permission对象。
    在Realm进行权限验证工作的上一层,也就是Authorizer中如果传递一个String表述的权限过来,Realm则使用PermissionResolver将其转换为Permission并开始验证工作。
    所有做权限验证的Realm都默认使用WildcardPermissionResovler实例。
    可能我们有更厉害的权限String语法,而且想让所有的Realm都支持这个语法。
    这个时候我们可以自己定义一个PermissionResolver并将其设置为全局PermissionResolver(global PermissionResolver)。
    比如在.ini配置文件中:

    globalPermissionResolver = com.foo.bar.authz.MyPermissionResolver
    securityManager.authorizer.permissionResolver = $globalPermissionResolver
    

    如果想配置一个全局PermissionResolver,每一个被注入的Realm都需要实现PermissionResolverAware接口。
    当然,如果是集成AuthorizingRealm就不用想这些了,因为...

    public abstract class AuthorizingRealm extends AuthenticatingRealm
            implements Authorizer, Initializable, PermissionResolverAware, RolePermissionResolverAware
    

    当然,也可以使用下面的方法显示地指定一个PermissionResolver。

    public void setPermissionResolver(PermissionResolver permissionResolver) {
        this.permissionResolver = permissionResolver;
        applyPermissionResolverToRealms();
    }
    

    或者在.ini中...

    permissionResolver = com.foo.bar.authz.MyPermissionResolver
    
    realm = com.foo.bar.realm.MyCustomRealm
    realm.permissionResolver = $permissionResolver
    

    相应地,RolePermissionResolver也是同理,只不过PermissionResolver是解析为Permission对象,而RolePermissionResolver是将角色String解析为Permission对象集合。

  • 相关阅读:
    zookeeper集群搭建
    kafka集群安装与配置
    Spring Task 定时任务配置与使用
    6.Spark SQL 及其DataFrame的基本操作
    10 期末大作业
    09 spark连接mysql数据库
    08 学生课程分数的Spark SQL分析
    从RDD创建DataFrame 07
    RDD 编程5
    05 RDD练习:词频统计
  • 原文地址:https://www.cnblogs.com/kavlez/p/4129094.html
Copyright © 2011-2022 走看看