Shiro - 受权

Authorization

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

 

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

咱们一般用三种元素进行受权操做,分别是:app

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

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

    一般状况下,资源都支持CRUD操做。可是,每一种操做对一个资源有着不一样的意义。
    因此咱们尽力将权限的粒度作的小一些。
    Permission仅仅声明对某个资源能够进行什么样的操做。
    好比:可否看到“删除用户”按钮?可否浏览用户列表页面?this

  • Roles: Role能够说是一系列动做的集合,一般将Role分配给User,User有没有操做权限归因于Role。
    Role有两种类型,分别是隐式(implicity)和显示(explicity)。编码

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


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

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

    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对象。token

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

    @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对象集合。

相关文章
相关标签/搜索