shiro实战系列(六)之Authorization(授权)

 

 

 

授权,又称作为访问控制,是对资源的访问管理的过程。换句话说,控制谁有权限在应用程序中做什么。 授权检查的例子是:该用户是否被允许访问这个网页,编辑此数据,查看此按钮,或打印到这台打印机?这些都是 决定哪些是用户能够访问的。

 

授权的要素:

Apache Shiro 中的权限代表着安全政策中最基础的元素。它们从根本上作出了对行为的声明,并明确表示可以在应用程序中做什么。一个格式良好的权限声明基本上描述了资源以及当 Subject 与这些资源进行交互时可能出现的行 为。

权限语句的一些例子:

(1)打开一个文件;

(2)查看'/user/list'网页;

(3)打印文档;

(4)删除用户'jsmith';

 

大多数资源将支持典型的 CRUD(创建,读取,更新,删除)操作,但任何对特定资源有意义的行为都是可以的。 基本的概念是,最小的许可声明是基于资源和行为的。 在查看权限时,最重要的可能是认识到许可声明没有谁可以执行代表行为的表现形式。它们仅仅只是在一个应用程 序中能做什么的声明语句。

 

Permissions represent behavior only 

许可声明仅能够反映行为(与资源类型相关的行为)。它们不反映是谁能够执行这样的行为。

定义(用户)被允许做什么(权限),是一个以某种方式分配给用户权限的运用。这通常是由应用程序的数据模型来完成的,并且不同应用程序间变化很大。 例如,权限能够被集合到一个角色中,该角色可以与一个或多个用户对象相关联。或者某些应用程序可以有一组可 以被分配一个角色的用户和组,传递的关联意味着该组中的所有用户都隐式地获得了该角色的权限。 如何授予用户权限可以有很多变化——应用程序决定如何基于应用的要求来建模。 我们稍后将讨论 Shiro 是如何确定一个 Subject 是否被允许做些什么。

 

权限粒度

 以上所有权限例子详细说明了在某一资源类型(入口,文件,客户等等)的行为(打开,阅读,删除等等)。在某些情况下,它们甚至可以指定非常细粒度的实例级的行为——例如,“删除”(行为)用户名为"jsmith"的“用户” (资源类型)。在 Shiro,你有能力来定义这些声明能够达到的精确粒度。   我们将在 Shiro 的 Permission 文档中更加详细地讨论权限粒度和许可声明的“等级”。

 

角色权限粒度

 

以上所有权限例子详细说明了在某一资源类型(入口,文件,客户等等)的行为(打开,阅读,删除等等)。在某些情况下,它们甚至可以指定非常细粒度的实例级的行为——例如,“删除”(行为)用户名为"jsmith"的“用户” (资源类型)。在 Shiro,你有能力来定义这些声明能够达到的精确粒度。   我们将在 Shiro 的 Permission 文档中更加详细地讨论权限粒度和许可声明的“等级”。

角色是一个命名的实体,通常代表一组行为或职责。这些行为演化为你在一个软件应用中能或者不能做的事情。角色通常是分配给用户帐户的,因此,通过分配,用户能够“做”的事情可以归属于各种角色。 有两种有效类型的角色,并且 Shiro 支持这两个概念:

(1)隐式角色:大多数人使用的角色作为一个隐式的构造:你的应用程序仅仅基于一个角色名就蕴含了一组行为 (也就是权限)。有了隐式角色,在软件级别上没有说“角色 X 被允许执行行为 A,B 和 C”。行为已被一个 单独的名字所蕴含。

Potentially。

Potentially Brittle Security  

虽然有比较简单和最常用的方法,隐式角色可能强加许多软件的维护和管理问题。 例如,如果你只是想添加或删除一个角色,或者重新定义一个角色的行为呢?你不得不返回到你的代码,并改写所 有你所有的角色检查以反映你的安全模型的改变,每次像这样的改变都是必不可少的!且不说这会招致运营成本(重新测试,经过 QA,关闭应用程序,新的角色下检查升级软件,重启该应用程序等)。 这对非常简单的应用程序可能没什么问题(例如,也许有一个“admin”的角色和“其他人”的角色)。但对于更复杂的或可配置的应用程序,这可能是你的应用程序整个生命周期中最主要问题,并且为你的软件造成一大笔维护费用。

 

(2)显式角色:一个显式角色本质上是一个实际许可声明的命名集合。在这种形式下,应用程序(以及 Shiro)确切地知道有没有一个特定的角色意味着什么。因为它是已知能不能够被执行的确切行为,没有猜测或暗示一 个特定的角色能或不能做什么。

Shiro 团队提倡使用权限和显式角色,而不是陈旧的隐式方法。你将会拥有更多的控制应用程序的安全经验。

Resource-Based Access Control(基于资源的访问控制)   

请务必阅读 Les Hazlewood 的文章,新的 RBAC:基于资源的访问控制, 其中包括深入使用权限和显式角色(以及它们在源代码上产生的积极影 响)而不是陈旧的隐式方法的好处

 

Users(用户)

用户实质上是指与应用程序有关的人。然而正如我们已经讨论的,Subject 才是 Shiro 的“用户”概念。 允许用户(Subjects)在你的应用程序中执行某些操作,是通过与他们的角色相关联或授予直接的权限。你的应用程序的数据模型定义了 Subject 是如何被允许做某事或不的。 例如,在你的数据模型中,也许你有一个实际的 User 类,而且你直接分配权限给 User 实例。或者,你也许只分配权限给角色,然后分配角色给用户,通过关联,用户延伸“有”的权限分配给自己的角色。或者你用"Group"的概念来代替这些东西。这些都随便你——使用什么使得你的程序有意义。 你的数据模型定义授权究竟是如和工作的。Shiro 依靠 Realm 来实现转换你的数据模型使其细节关联到一种 Shiro 能 够理解的格式。

 

我们一会儿将讨论 Realms 是如何做到这一点的。

最终,你的 Realm 的实现是与你的数据源(RDBMS,LDAP 等)进行通信。所以,你的 realm 就是告诉 Shiro 是否存在角色或权限。在你的授权模型结构和定义上你有充分的控制权。

 

Authorizing Subjects(授权的 Subjects)

在 Shiro 中执行授权可以有 3 种方式:

    (1)编写代码——你可以在你的 Java 代码中用像 if 和 else 块的结构执行授权检查。  (2)JDK的注解——你可以添加授权注解给你的 Java 方法。 

(3)JSP/GSP 标签库——你可以控制基于角色和权限的 JSP 或者 GSP 页面输出。

 

(1)Programmatic Authorization(编程授权)

 也许最简单和最常见的方式来执行授权是直接以编程方式与当前 Subject 实例交互。

(2) Role-Based Authorization(基于角色的授权)

如果你想进行基于简单/传统的隐式角色名来控制访问,你可以执行角色检查

 

Role checks(角色检查)

如果你只是简单的想检查当前的 Subject 是否拥有一个角色,你可以在 Subject 实例上调用变体的 hasRole*方法。 例如,判断一个 Subject 是否拥有一个特别的(单一的)角色,你可以通过调用 subject.hasRole 方法,并作出相应的反应:

 

有几个面向角色的 Subject 方法可以调用,一起取决于你的需要:

 

 

Role Assertions(角色断言)

另一种方法通过检查布尔值来判断 Subject 是否拥有一个角色,你可以简单地断言它们有一个预期的角色在逻辑被执行之前。如果 Subject 没有预期的角色,AuthorizationException 将会被抛出。如果它们有预期的角色,断言将悄悄 地执行,并且逻辑将如预期般继续。

例如:

 

通过使用 hasRole*方法的一个好处就是代码可以变得清洁,由于你不需要创建你自己的 AuthorizationException 如果 当前的 Subject 不符合预期条件(如果你不想的话)。 有几个你能

调用的面向角色的 Subject 断言方法,取决于你的需要:

 

Permission-Based Authorization(基于权限的授权)

如前所述在我们所概述的角色,往往一个更好的方式执行访问控制是通过基于权限的授权。基于权限的授权,由于 它与你的应用程序的原始功能(以及应用程序核心资源上的行为)紧密的关联在一起的,基于权限的授权源代码会 在你的功能改变时改变,而不是在安全政策改变时改变。这意味着代码很少会被影响对比相似的基于角色的授权的 代码。

 

Permission Checks(权限检查)

如果你想进行检查,看一个 Subject 是否被允许做某事,你可以调用各种 isPermitted*方法的变种。检查权限主要有 两个方式——基于对象的权限实例或代表权限的字符串。

 

Object-based Permission Checks(基于对象的权限检查)

执行权限检查的一个可行方法是实例化 org.apache.shiro.authz.Permission 接口的一个实例,并把它传递给接收权限 实例的*isPermitted 方法。 例如,请考虑以下情况:在办公室有一台打印机,具有唯一标识符 laserjet4400n。我们的软件需要检查当前用户是 否被允许在该打印机上打印文档在我们允许他们按下“打印”按钮之前。上述情况的权限检查可以明确地像这样表达:

 

在这个例子中,我也看到了一个非常强大的实例级的访问控制检查的例子——限制行为的能力是基于个人的数据实例。 基于对象的权限是很有用的,如果:

(1)你想编译时类型安全 

(2)你想保证权限被描述和使用是正确的 

(3)你想显式控制许可解析逻辑(被称作许可蕴含的逻辑,基于权限接口的 implies 方法)是如何执行的。

 (4)你想保证权限反映到应用程序资源是准确的(例如,也许权限类可以在能够基于项目的域模型的项目编译时 自动生成)

 

有几个你能调用的面向权限的 Subject 方法,取决于你的需要:

 

String-based permission checks(基于字符串的权限检查)

基于对象的权限可以是很有用的(编译时类型安全,保证行为,定制蕴含逻辑等),它们有时对应用程序来说会感 到有点“笨手笨脚”的。另一种方法是使用正常的字符串来表示权限。

实例。

例如,基于上面的打印权限的例子上,我们可以重新制订与之前检查相同的基于字符串的权限检查:

 

这个例子还显示了相同的实例级权限检查,但权限的重要组成部分——打印机(资源类型),打印(行为),以及 laserjet4400n(实例 ID)——都用一个字符串表示。 这个特别的例子显示了一个特殊冒号分隔的格式,它由 Shiro 默认的 org.apache.shiro.authz.permission.WildcardPermission 实现来定义,其中大多数人会找到适合自己的格式。 也就是说,上面的代码块(大部分)是下面代码的简化:

 

WildcardPermission token 规定和构造操作的格式在 Shiro 的 Permission 文档中被深入的涉及到。 除了上面的字符串默认的 WildcardPermission 格式,你可以创建和使用自己的字符串格式如果你喜欢的话。我们将在 Realm Authorization 这一节讨论如何去做。 基于字符串的权限是很有帮助的,由于你不必被迫实现一个接口,而且简单的字符串易于阅读。其缺点是,你不具备类型安全,如果你需要更为复杂的行为将超出了字符串所能代表的范围,你就得实现你自己的基于权限接口的权限对象。在实际中,大部分的Shiro终端用户为了简洁选择基于字符串的方式,但最终你应用程序的需求会决定哪 一个更好。 像基于对象的权限检查方法一样,也有字符串的变体来支持基于字符串的权限检查:

 

Permission Assertions(权限断言)

作为检查一个布尔值来判断 Subject 是被允许做某事的一种替代,你可以在逻辑被执行之前简单地断言他们是否拥 有预期的权限。如果该 Subject 是不被允许,AuthorizationException 异常将会被抛出。如果他们如预期的被允许,断 言将安静地执行,逻辑也将如预期般继续。

例如:

或者,同样的检查,使用字符串权限:

 

通过使用 hasRole*方法的一个好处就是代码可以变得清洁,由于你不需要创建你自己的 AuthorizationException 如果 当前的 Subject 不符合预期条件(如果你不想的话)。 有几个你能调用的面向权限的 Subject 断言方法,取决于你的需要:

 

Annotation-based Authorization(基于注解的授权)

 除了 Subject API 的调用,Shiro 提供 Java 5+注解的集合,如果你喜欢以注解为基础的授权控制

configuration(配置)

在你可以使用 Java 注释之前,你需要在你的应用程序中启用 AOP 支持。虽然现在有许多不同的 AOP 框架,但不幸 的是,在应用程序中没有一个使用 AOP 的标准。

 The RequiresAuthentication annotation(RequiresAuthentication 注解) RequiresAuthentication 注解要求当前 Subject 已经在当前的 session 中被验证通过才能被注解的类/实例/方法访问或 调用。 例如:

 

The RequiresGuest annotation(RequiresGuest 注解)

RequiresGuest 注解要求当前的 Subject 是一个"guest",也就是说,他们必须是在之前的 session 中没有被验证或记住 才能被注解的类/实例/方法访问或调用。

 

例如:

 

这通常等同于接下来的基于 Subject 的逻辑

 

 

The RequiresPermissions annotation(RequiresPermissions 注解)

RequiresPermissions 注解要求当前的 Subject 被允许一个或多个权限,以便执行注解的方法。

 

The RequiresRoles annotation(RequiresRoles 注解) RequiresRoles 注解要求当前的 Subject 拥有所有指定的角色。如果他们没有,则该方法将不会被执行,而且 AuthorizationException 异常将会被抛出。 例如:

 

 

The RequiresUser annotation(RequiresUser 注解)

RequiresUser 注解需要当前的 Subject 是一个应用程序用户才能被注解的类/实例/方法访问或调用。一个“应用程序 用户”被定义为一个拥有已知身份,或在当前 session 中由于通过验证被确认,或者在之前 session 中的'RememberMe' 服务被记住。

 

 

Authorization Sequence(授权顺序)

现在我们已经知道了基于当前 Subject 上如何执行授权,让我们看看当授权调用时,Shiro 内部会发生什么。 我们采用了 Architecture 那一章的体系结构图,并只留下与 authorization 有关的组件突出显示。每个数字代表授权 过程中的一个步骤:

 

 

Step 1:应用程序或框架代码调用任何 Subject 的 hasRole*, checkRole*, isPermitted*, 或者 checkPermission*方法的变体,传递任何所需的权限或角色代表。

 

Step 2:Subject 的实例,通常是 DelegatingSubject(或子类)代表应用程序的 SecurityManager 通过调用 securityManager 的几乎各自相同的 hasRole*, checkRole*, isPermitted*,或 checkPermission*方法的变体(SecurityManager 实现 org.apache.shiro.authz.Authorizer 接口,他定义了所有 Subject 具体的授权方法)。

 

Step 3:SecurityManager,作为一个基本的“保护伞”组件,接替/代表它内部的 org.apache.shiro.authz.Authorizer 实例通过调用 authorizer 各自的 hasRole*, checkRole*, isPermitted*, 或者 checkPermissions*方法。默认情况下, authorizer 实例是一个 ModularRealmAuthorizer 实例,它支持协调任何授权操作过程中的一个或多个 Realm 实例。

 

Step 4:每个配置好的 Realm 被检查是否实现了相同的 Authorizer 接口。如果是,Realm 各自的 hasRole*, checkRole*, isPermitted*,或 checkPermission*方法将被调用。  

 

ModularRealmAuthorizer

如前所述,Shiro SecurityManager 的实现默认是使用一个 ModularRealmAuthorizer 实例。ModularRealmAuthorizer 同样支持单一的 Realm,以及那些与多个 Realm 的应用。

对于任何授权操作,ModularRealmAuthorizer 将遍历其内部的 Realm 集合,并按迭代顺序与每一个进行交互。每个 Realm 的交互功能如下:

 

1. 如果 Realm 自己实现了 Authorizer 接口,它的各个 Authorizer 方法(hasRole*, checkRole*, isPermitted*, 或 checkPermission*)将被调用。

  (1) 如果 Realm 的方法导致异常,该异常将会以 AuthorizationException 的形式传递给调用者。这将短路授权 过程,同时任何剩余的 Realm 将不会被该授权操作所访问。

  (2) 如果该 Realm 的方法是一个返回布尔值的 hasRole*或者 isPermitted*的变体,并且该返回值为 true,真 值将会立即被返回,同时任何剩余的 Realm 都将被短路。这种行为作为提高性能的一种存在,如果该行 为被一个 Realm 允许,这意味着该 Subject 也是被允许的。这有利于安全政策,每一处都是默认被禁止的 情况下,一切都明确允许的,这是安全政策最安全的类型。

2. 如果 Realm 不实现 Authorizer 接口,它会被忽略。

 

Realm Authorization Order(Realm 的授权顺序)

 

需要重要指出的是,尤其是身份验证,ModularRealmAuthorizer 将以迭代顺序与 Realm 实例进行交互。 ModularRealmAuthorizer 根据 SecurityManager 的配置获得对 Realm 实例的访问。当执行授权操作时,它会遍历该集合,同时对于每一个自己实现 Authorizer 接口的 Realm,调用 Realm 各自的 Authorizer 方法(如 hasRole*, checkRole*, isPermitted*,或 checkPermission*)。

 

Configuring a global PermissionResolver(配置全局的 PermissionResolver)

 

当执行基于字符串的权限检查是,大多数 Shiro 的默认 Realm 实现首先将该字符串转换成一个实际的 Permission 实例,在执行权限 implication 逻辑之前。 这是因为 Permission 是基于 implication 逻辑评估的,而不是直接的 equality 检查(见 Permission 文档有关更多 implication 和 equality 的对比)。Implication 逻辑对比通过字符串比较能够更好的在代码中体现。因此,大多数 Realm 需要转换,或者将提交的权限字符串解析成相应的代表权限的实例。 为了帮助这种转换,Shiro 支持 PermissionResolver 的概念。大多数 Shiro Realm 的实现使用一个 PermissionResolver 以支持他们的基于字符串权限的 Authorizer 接口方法的实现:当其中一种方法在 Realm 上被调用是,它将使用 PermissionResolver 把该字符串转换成一个权限实例,并用这种方式来执行检查。 所有 Shiro Realm 的实现默认是内部的 WildcardPermissionResolver,它采用 Shiro 的 WildcardPermission 字符串格式。 如果你想创建自己的 PermissionResolver 的实现,也许是为了支持自己的权限字符串语法,而且你想要所有配置的 Realm 实例支持该语法,你可以将你的 PermissionResolver 设置为全局的,这样所有的 Realm 能够用一个配置。

 

Configuring a global RolePermissionResolver(配置全局的 RolePermissionResolver)

与 PermissionResolver 在概念上相似,RolePermissionResolver 有能力代表需要的权限实例,通过一个 Realm 执行权限检查。 然而,与一个 RolePermissionResolver 的关键区别是输入的字符串是一个角色名,而不是一个权限字符串。 RolePermissionResolver 能够在 Realm 内部使用,当需要将一个角色名转换成一组具体的权限实例时。 这是一个特别有用的特征用来支持旧的或不灵活的,可能没有权限概念的数据源。 例如,许多 LDAP 目录存储了角色名(或组名),但是不支持关联角色名到具体的权限由于他们没有“权限”的概 念。一个基于Shiro 的应用程序能够使用存储在 LDAP 的角色名,还能实现一个 RolePermissionResolver 来转化 LDAP 名到一组显式的权限来执行首选的显式的访问控制。权限关联将会被存储在另一个数据仓库,可能是一个本地数据 库。 由于这种转换角色名到权限的概念非常特定于应用程序,Shiro 默认 Realm 的实现并不使用它们。 然而,如果你想创建你自己的 RolePermissionResolver,并有多个你想配置的 Realm 的实现,你可以将你的 RolePermissionResolver 设置为全局的,这样所有的 Realm 都能够用一个配置。

 

Configuring a global RolePermissionResolver(配置全局的 RolePermissionResolver)

与 PermissionResolver 在概念上相似,RolePermissionResolver 有能力代表需要的权限实例,通过一个 Realm 执行权限检查。 然而,与一个 RolePermissionResolver 的关键区别是输入的字符串是一个角色名,而不是一个权限字符串。 RolePermissionResolver 能够在 Realm 内部使用,当需要将一个角色名转换成一组具体的权限实例时。 这是一个特别有用的特征用来支持旧的或不灵活的,可能没有权限概念的数据源。 例如,许多 LDAP 目录存储了角色名(或组名),但是不支持关联角色名到具体的权限由于他们没有“权限”的概 念。一个基于Shiro 的应用程序能够使用存储在 LDAP 的角色名,还能实现一个 RolePermissionResolver 来转化 LDAP 名到一组显式的权限来执行首选的显式的访问控制。权限关联将会被存储在另一个数据仓库,可能是一个本地数据 库。 由于这种转换角色名到权限的概念非常特定于应用程序,Shiro 默认 Realm 的实现并不使用它们。 然而,如果你想创建你自己的 RolePermissionResolver,并有多个你想配置的 Realm 的实现,你可以将你的 RolePermissionResolver 设置为全局的,这样所有的 Realm 都能够用一个配置。

 

 

 

RolePermissionResolverAware  如果你想配置一个全局的 RolePermissionResolver,每个用来接收配置的 RolePermissionResolver 的 Realm 必须实现 RolePermissionResolverAware 接口。这样保证了配置的全局的 RolePermissionRosolver 实例能够被每个支持该配置的 Realm 转发。

 

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困扰,你可以随时 显式地配置一个拥有 RolePermissionResolver 实例的 Realm(假设有一个兼容 JavaBean 的 setRolePermissionResolver 的方法):

 

 

 

 

RolePermissionResolverAware  如果你想配置一个全局的 RolePermissionResolver,每个用来接收配置的 RolePermissionResolver 的 Realm 必须实现 RolePermissionResolverAware 接口。这样保证了配置的全局的 RolePermissionRosolver 实例能够被每个支持该配置的 Realm 转发。

 

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困扰,你可以随时 显式地配置一个拥有 RolePermissionResolver 实例的 Realm(假设有一个兼容 JavaBean 的 setRolePermissionResolver 的方法):

 

 

如果你不希望使用全局的 RolePermissionResolver 或你不想被 RolePermissionResolverAware 接口所困扰,你可以随时 显式地配置一个拥有 RolePermissionResolver 实例的 Realm(假设有一个兼容 JavaBean 的 setRolePermissionResolver 的方法):

 

 

Custom Authorizer(自定义授权者)

如果你的应用程序使用多个 realm 来执行授权,并且 ModularRealmAuthorizer 默认基于简单的迭代,短路授权行为 不符合你的要求,你很有可能想创建一个自定义的授权者,并配置相应的 SecurityManager。

 

Understanding Permissions in Apache Shiro

Shiro 将权限定义为一个规定了明确行为或活动的声明。这是一个在应用程序中的原始功能语句,仅此而已。权限 是在安全策略中最低级别的构造,且它们明确地定义了应用程序只能做“什么”。   它们从不描述“谁”能够执行这些动作。

 

 

一些权限的例子:

(1)    打开文件

(2)    浏览'/user/list'页面

(3)     打印文档

(4)    删除'jsmith'用户  

 

规定“谁”(用户)允许做“什么”(权限)在某种程度上是分配用权限的一种习惯做法。这始终是通过应用程序 数据模型来完成的,并且在不同应用程序之间差异很大。

 

例如,权限可以组合到一个角色中,且该角色能够关联一个或多个用户对象。或者某些应用程序能够拥有一组用户, 且这个组可以被分配一个角色,通过传递的关联,意味着所有在该组的用户隐式地获得了该角色的权限。   如何授予用户权限可以有很多变化——应用程序基于应用需求来决定如何使其模型化。

 

 Wildcard Permissions 上述的权限例子,“打开文件”、“浏览'/user/list'页面”等都是有效的权限语句。然而,将这些解释为自然语言字符串,并判断用户是否被允许执行该行为在计算上是非常困难的。   因此,为了使用易于处理且仍然可读的权限语句,Shiro 提供了强大而直观的语法,我们称之为 WildcardPermission。

 

Simple Usage

假设你想要保护到贵公司打印机的访问,使得某些人能够打印到特定的打印机,而其他人可以查询当前有哪些工作 在队列中。 

一个极其简单的方法是授予用户"queryPrinter"权限。然后你可以检查用户是否具有 queryPrinter 权限通过调用

 

 

简单的权限字符串可能在简单的应用程序中工作的很好,但它需要你拥有像"printPrinter","queryPrinter", "managePrinter"等权限。你还可以通过使用通配符授予用户"*"权限(赋予此权限构造它的名字),这意味着他们在 整个应用程序中拥有了所有的权限。   但使用这种方法不能说用户拥有“所有打印机权限”。由于这个原因,Wildcard Permissions(通配符权限)支持多 层次的权限管理。  

 

Multiple Parts

通配符权限支持多层次或部件(parts)的概念。例如,你可以通过授予用户权限来调整之前那个简单的例子。

 

 

在这个例子中的冒号是一个特殊字符,它用来分隔权限字符串的下一部件。   在该例中,第一部分是权限被操作的领域(打印机),第二部分是被执行的操作(查询)。上面其他的例子将被改 为:

 

对于能够使用的部件是没有数量限制的,因此它取决于你的想象,依据你可能在你的应用程序中使用的方法。

 

Multiple Vaules

每个部件能够保护多个值。因此,除了授予用户"printer:print"和"printer:query"权限外,你可以简单地授予他们一个:

它能够赋予用户 print 和 query 打印机的能力。由于他们被授予了这两个操作,你可以通过调用下面的语句来判断用 户是否有能力查询打印机:

该语句将会返回 true

 

Multiple Vaules

每个部件能够保护多个值。因此,除了授予用户"printer:print"和"printer:query"权限外,你可以简单地授予他们一个:

它能够赋予用户 print 和 query 打印机的能力。由于他们被授予了这两个操作,你可以通过调用下面的语句来判断用 户是否有能力查询打印机:

 

All Values

如果你想在一个特定的部件给某一用户授予所有的值呢?这将是比手动列出每个值更为方便的事情。同样,基于通 配符的话,我也可以做到这一点。若打印机域有 3 个可能的操作(query,print 和 manage),可以像下面这样:

然后,任何对"printer:XXX"的权限检查都将返回 true。以这种方式使用的通配符比明确地列出操作具有更好的尺度, 如果你不久为应用程序增加了一个新的操作,你不需要更新使用通配符那部分的权限。   最后,在一个通配符权限字符串中的任何部分使用通配符 token 也是可以的。例如,如果你想对某个用户在所有领 域(不仅仅是打印机)授予"view"权限,你可以这样做:

 

Instance-Level Access Control

另一种常见的通配符权限用法是塑造实例级的访问控制列表。在这种情况下,你使用三个部件——第一个是域,第 二个是操作,第三个是被付诸实施的实例。

 

因此像下面这个例子:

第一个定义了查询拥有 ID lp7200 的打印机的行为。第二条权限定义了打印到拥有 ID epsoncolor 的打印机的行为。 如果你授予这些权限给用户,那么他们能够在特定的实例上执行特定的行为。然后你可以在代码中做一个检查:

 

这是体现权限的一个极为有效的方法。但同样,为所有的打印机定义多个实例 ID 能很好的扩展,尤其是当新的打 印机添加到系统的时候。你可以使用通配符来代替:

 

这个做到了扩展,因为它同时涵盖了任何新的打印机。你甚至可以运行访问所有打印机上的所有操作:

 

或在一台打印机上的所有操作:

 

或甚至特定的操作:

"*"通配符,","子部件分离器可用于权限的任何部分。

 

Missing Parts 最后要注意的是权限分配:缺少的部件意味着用户可以访问所有与之匹配的值,换句话说,

 

 

 

Checking Permissions

虽然权限分配使用通配符较为方便且具有扩展性("printer:print:*" = print to any printer),但在运行时的权限检查应 该始终基于大多数具体的权限字符串。   例如,如果用户有一个用户界面,他们想打印一份文档到 lp7200 打印机,你应该通过执行这段代码来检查用户是否 被允许这样做。

 

 

 

为什么?

因为第二个例子表明“对于下面的代码块的执行,你必须能够打印到任何打印机”。但请记住"pinter:print" 是等价于"priner:print:*"的!   因此,这是一个不正确的检查。如果当前用户不具备打印到任何打印机的能力,仅仅只有打印到 lp7200 和 epsoncolor 的能力,该怎么办呢?那么上面的第二个例子也绝不允许他们打印到 lp7200 打印机,即使他们已被赋予了相应的能 力!   因此,经验法则是在执行权限检查时,尽可能使用权限字符串。当然,上面的第二块可能是在应用程序中别处的一 个有效检查,如果你真的想要执行该代码块,如果用户被允许打印到任何打印机(令人怀疑的,但有可能)。你的 应用程序将决定检查哪些有意义,但一般情况下,越具体越好。  

 

 

Implication, not Equality

为什么运行时权限检查应该尽可能的具体,但权限分配可以较为普通?这是因为权限检查是通过蕴含的逻辑来判断 的——而不是通过相等检查。   也就是说,如果一个用户被分配了 user:*权限,这意味着该用户可以执行 user:view 操作。"user:*"字符串明显不等 于"user:view",但前者包含了后者。"user:*"描述了"user:view"所定义的功能的一个超集。   为了支持蕴含规则,所有的权限都被翻译到实现 org.apache.shiro.authz.Permission 接口的的对象实例中。这是以便 蕴含逻辑能够在运行时执行,且蕴含逻辑通常比一个简单的字符串相等检查更为复杂。所有在本文档中描述的通配 符行为实际上是由 org.apache.shiro.authz.permission.WildcardPermission 类实现的。下面是更多的一些通过蕴含逻辑 访问的通配符权限字符串:

 

 

 

 

 

 

Performance Considerations

权限检查比简单的相等比较要复杂得多,因此运行时的蕴含逻辑必须执行每个分配的权限。当使用像上面展示的权 限字符串时,你正在隐式地使用 Shiro 默认的 WildcardPermission,它能够执行必要的蕴含逻辑。   Shiro 对 Realm 实现的默认行为是,对于每一个权限验证(例如,调用 subject.isPermitted),所有分配给该用户的 权限(在他们的组,角色中,或直接分配给他们)需要为蕴含逻辑进行单独的检查。Shiro 通过首次成功检查立即 返回来“短路”该进程以提高性能,但它不是一颗银弹。   这通常是极快的,当用户,角色和权限缓存在内存中且使用了一个合适的 CacheManager 时,在 Shiro 不支持的 Realm 实现中。只要知道使用此默认行为,当权限分配给用户或他们的角色或组增加时,执行检查的时间一定会增加。   如果一个 Realm 的实现者有一个更为高效的方式来检查权限并执行蕴含逻辑,尤其它如果是基于应用程序数据模型 的,他们应该实现它作为 Realm isPermitted* 方法实现的一部分。默认的 Realm/WildcardPermission 存在的支持覆盖 了大多数用例的 80~90%,但它可能不是在运行时拥有大量权限需要存储或检查的应用程序的最佳解决方案。  

 

shiro的授权简单的一句话概述,该用户对应的角色有访问那些资源的权限。

shiro作为javaee项目开发是比较常用的,很多开源项目也在用,什么gun,jeeste,jeecg等。总而言之,springsecurity并未shiro用的广。

所以说学shiro,以后java权限方面就再也不怕了。借用一句话概述:“自从有了shiro,什么样的权限管理机制我都不怕了”。

 

posted @ 2018-06-10 00:39  挑战者V  阅读(591)  评论(0编辑  收藏  举报