# 6. Authorization 授權
# 6. Authorization 授權
授權,亦即訪問控制,是管理資源訪問的過程,換言之,也就是控制在一個程序中“誰”有權利訪問“什么”。
授權的例子有:是否允許這個用戶查看這個頁面,編輯數據,看到按鈕,或者從這臺打印機打印?這些決定是一個用戶可以訪問什么的決斷。
## Elements of Authorization 元素
授權有三個核心元素,在 Shiro 中我們經常要用到它們:權限(permissions)、角色(roles)和用戶(users)。
### Permissions
權限是 Apache Shiro 中安全策略最基本的元素,它們是一組關于行為的基本指令,以明確表示在一個程序中什么可以做。一個很好定義的權限指令必須描述資源以及當一個 Subject 與這些資源交互時什么動作可以執行。
下面是一些權限指令的例子:
- 打開一個文件;
- 查看“/user/list”頁面;
- 打印文檔;
- 刪除“Jsmith”用戶。
大部分資源都支持基本的 CRUD(create,read,update,delete)操作,但對于特定資源類型,任何動作都是可以的。權限設置最基礎的思想是在資源和動作的基礎上設置最小量的權限指令。
當看到權限時,最重要的一點是要是認識到一個權限指令不是描述“誰”可以執行這個動作,而是描述“什么”可以做的指令。
*權限只描述行為*
*權限指令只描述行為(和資源相關的動作),并不關心“誰”有能力執行這個動作。*
定義“誰”(用戶)被允許做“什么”(權限)需要用一些方法將權限賦給用戶,這通常取決于程序的數據模型而且經常在程序中發生改變。
例如,一組權限可以歸于一個角色而角色與一個或多個用戶對象關聯,或者一些程序可以有一組用戶而一個組可以指定一個角色,在這里關系將被傳遞也就是說組內用戶隱含被賦予角色的權限。
有很多方式可以將權限賦予用戶--程序根據需求決定如何設計。
我們稍后討論 Shiro 如何判斷一個 Subject 是否被允許。
#### Permission Granularity 權限粒度
上面的權限示例都是針對資源(門、文件、客戶等)指定的動作(打開、讀、刪除等),在一些場景中,他們也會指定非常細粒度的“實例級別”行為--例如,“刪除”(delete)名為“Jsmith”(實例標識)的“用戶”(資源類型),在 Shiro 中,你可以精確定義指令到你所能細化到的程度。
我們在 Shiro 的 [Permissions](6.1.%20Permissions%20%E6%9D%83%E9%99%90.md) 文檔中詳細討論權限粒度和權限指令的“級別”。
### Roles
角色是一個實體名,代表一組行為或職責,這些行為在程序中轉換為你可以或者不能做的事情。角色通常賦給用戶帳號,關聯后,用戶就可以“做”屬于不同角色的事情。
有兩種有效的角色指定方式,Shiro 都支持。
- 權限隱含于角色中:大部分用戶將權限隱含于創建的角色中:程序只是在一個角色名稱上隱含了一組行為(也就是權限),使用時,在軟件級別不會說“某角色允許執行行為A、B和C”,而是將行為隱含于一個單獨的角色名字中。
*潛在的安全隱患*
*雖然這是一個非常簡單和常用的方法,但隱含的角色可能會增加軟件的維護成本和管理問題。*
*例如,如果你想增加或刪除一個角色,或者重定義角色的行為怎么辦?你不得不重新打開代碼,修改所有對更改后的角色的檢測,每次都需要這樣做,這還沒提到其引起的執行代價(重測試,通過質量驗證,關閉程序,升級軟件,重啟程序等)。*
*對于簡單程序這種方法可能適用(比如只有一個'admin'角色和'everyone else'角色),但復雜的程序中,這會成為你程序生命周期中一個主要的問題,會給你的軟件帶來很大的維護代價。*
- 明確為角色指定權限:明確為角色指定權限本質上是一組權限指令的名稱集,在這種形式下,程序(以及 Shiro)準確知道一個特定的角色是什么意思,因為它確切知道某行為是否可以執行,而不用去猜測特定的角色可以或不可以做什么。
Shiro 團隊提倡使用權限和明確為角色指定權限替代原始的將權限隱含于角色中的方法,這樣你可以對程序安全提供更強的控制。
*基于資源的訪問控制*
*讀一下這個文章:[新的RBAC:基于資源的權限管理(Resource-Based Access Control)](http://www.waylau.com/new-rbac-resource-based-access-control/),這篇文章深入討論了使用權限和明確為角色指定權限代替舊的將權限隱含于角色中方法的好處(以及對源代碼的影響)。*
### Users用戶
一個用戶本質上是程序中的“誰”,如同我們前面提到的,Subject 實際上是 shiro 的“用戶”。
用戶(Subjects)通過與角色或權限關聯確定是否被允許執行程序內特定的動作,程序數據模型確切定義了 Subject 是否允許做什么事情。
例如,在你的數據模型中,你定義了一個普通的用戶類并且直接為其設置了權限,或者你只是直接給角色設置了權限,然后將用戶與該角色關聯,通過這種關聯,用戶就“有”了角色所具備的權限,或者你也可以通過“組”的概念完成這件事,這取決于你程序的設計。
數據模型定義了如何進行授權,Shiro 依賴一個 [Realm](7.%20Realms.md) 實現將你的數據模型關聯轉換成 Shiro 可以理解的內容,我們將稍后討論 Realms。
*最終,是 [Realm](7.%20Realms.md) 與你的數據源(RDBMS,LDAP等)做交流,Realm 用來告知Shiro 是否角色或權限是否存在,你可以完全控制你的授權模型如何創建和定義。*
## Authorizing Subjects 授權對象
在 Shiro 中執行授權可以有三種途徑:
- 程序代碼--你可以在你的 JAVA 代碼中執行用類似于 if 和 else 的結構來執行權限檢查。
- JDK 注解--你可以在你的 JAVA 方法上附加權限注解
- JSP/GSP 標簽--你可以基于角色和權限控制 JSP 或 GSP 頁面輸出內容。
### Programmatic Authorization 程序中檢查授權
直接在程序中為當前 Subject 實例檢查授權可能是最簡單也最常用的方法。
#### Role-Based Authorization 基于角色的授權
如果你要基于簡單/傳統的角色名進行訪問控制,你可以執行角色檢查:
##### Role Checks 角色檢查
如果你想簡單地檢查一下當前Subject是否擁有一個角色,你可以在一個實例上調用 hasRole\* 方法。
例如,查看一個 Subject 是否有特定(單獨)的角色,你可以調用subject.[hasRole(roleName)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#hasRole(java.lang.String))方法,做出相應的反饋。
```
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.hasRole("administrator")) {
//顯示 admin 按鈕
} else {
//不顯示按鈕? 灰色嗎?
}
```
下面是你可以根據需要調用的函數:
Subject 方法 描述 [hasRole(String roleName)](static/current/apidocs/org/apache/shiro/subject/Subject.html#hasRole(java.lang.String)) 如果Subject指定了特定的角色返回真,否則返回假; [hasRoles(List<String> roleNames)](static/current/apidocs/org/apache/shiro/subject/Subject.html#hasRoles(java.util.List)) 返回一個與參數順序相對應的hasRole結果數組,當一次有多個角色需要檢測時非常有用(如定制一個復雜的視圖) [hasAllRoles(Collection<String> roleNames)](static/current/apidocs/org/apache/shiro/subject/Subject.html#hasAllRoles(java.util.Collection)) 如果Subject具備所有角色返回真,否則返回假。##### Role Assertions 角色判斷
還有另一個方法檢測 Subjet 是否是指定為某個角色,你可以在的代碼執行之前簡單判斷他們是否是所要求的角色,如果 Subject 不是所要求的角色, [AuthorizationException](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/AuthorizationException.html) 異常將被拋出,如果是所要求的角色,判斷將安靜地執行并按期望順序執行下面的邏輯。
例如:
```
Subject currentUser = SecurityUtils.getSubject();
//保證當前用戶是一個銀行出納員
//因此允許開立帳戶:
currentUser.checkRole("bankTeller");
openBankAccount();
```
與 hasRole\* 方法相比,這種方法的好處在于代碼更為清晰,如果當前Subject 不滿足所需條件你不需要建立你自己的AuthorizationExceptions 異常(如果你不想那么做)。
下面是你可以根據需要調用的函數:
Subject 方法 描述 [checkRole(String roleName)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkRole(java.lang.String)) 如果Subject被指定為特定角色則安靜地返回否則拋出AuthorizationException異常; [checkRoles(Collection roleNames)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkRoles(java.util.Collection)) 如果Subject被指定了所有特定的角色則安靜地返回否則拋出AuthorizationException異常; [checkRoles(String... roleNames)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkRoles(java.lang.String...)) 和上面的checkRoles具有相同的效果,但允許Java5的變參形式。 #### Permission-Based Authorization 基于權限的授權
就像我們上面在角色概述中提到的,通過基于權限的授權執行訪問控制是更好的方法。基于權限的授權,因為其與程序功能(以及程序核心資源上的行為)緊密聯系,基于權限授權的源代碼在程序功能改變時才需要改變,而與安全策略無關。這意味著與同樣基于角色的授權相比,對代碼的影響更少。
##### Permission Checks 權限檢查
如果你希望檢查一個 Subject 是否允許做某件事情,你可以調用isPermitted\* 方法的變形,有兩種主要方式檢查授權--基于對象的權限實例和基于字符串的權限表示。
##### Object-based Permission Checks 基于對象的權限檢查
執行權限檢查的一種方法是實例化一個Shiro的[org.apache.shiro.authz.Permission](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Permission.html)接口并且將它傳遞給接收權限實例的\*isPermitted 方法。
例如,假設一下以下情景:辦公室里有一臺唯一標識為 laserjet4400n 的打印機,在我們向用戶顯示打印按鈕之前,軟件需要檢查當前用戶是否允許用這臺打印機打印文檔,檢查權限的方式會是這樣:
```
Permission printPermission = new PrinterPermission("laserjet4400n", "print");
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isPermitted(printPermission)) {
//顯示 打印 按鈕
} else {
//不顯示按鈕? 灰色嗎?
}
```
在這個例子中,我們同樣看到了一個非常強大的實例級別的訪問控制檢查--在單獨數據實例上限制行為的能力。
基于對象的權限對下列情況非常有用:
- 希望編譯期類型安全;
- 希望確保正確地引用和使用的權限;
- 希望對權限判斷邏輯(稱作權限隱含邏輯,基于權限接口的 [implies](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Permission.html#implies(org.apache.shiro.authz.Permission))方法)執行方式進行明確控制;
- 希望確保權限正確地反映程序資源(例如,在一個對象域模型上創建一個對象時,權限類可能自動產生)。
下面是你可以根據需要調用的函數:
Subject 方法 描述 [isPermitted(Permission p)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermitted(org.apache.shiro.authz.Permission)) 如果Subject允許執行特定權限實例綜合指定的動作或資源訪問權返回真,否則返回假; [isPermitted(List perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermitted(java.util.List)) 按參數順序返回isPermitted的結果數組,如果許多權限需要檢查時非常有用(如定制一個復雜的視圖) [isPermittedAll(Collection perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermittedAll(java.util.Collection))) 如果Subject擁有指定的所有權限返回真,否則返回假。 ##### String-based permission checks 基于字符串的權限檢查
雖然基于對象的權限檢查很有用(編譯期類型安全,對行為擔保,定制隱含邏輯等),但在許多程序里有時候感覺有點笨重,另一種選擇是用普通的字符串來代表權限。
例如,對于上面打印權限的例子,我們可以使用字符串權限檢查達到同樣的結果
```
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isPermitted("printer:print:laserjet4400n")) {
//顯示 打印 按鈕
} else {
//不顯示按鈕? 灰色嗎?
}
```
這個例子同樣實現了實例級別的權限檢查,但是所有主要權限部件--printer(資源類型)、print(動作)、laserjet4400n(實例ID)都表現為一個字符串。
上面的例子展示了一種以冒號分割的特殊形式的字符串,定義于Shiro的 [org.apache.shiro.authz.permission.WildcardPermission](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/WildcardPermission.html)中,它適合大多數用戶的需求。
上面的代碼塊基本上是下面這段代碼的縮寫:
```
Subject currentUser = SecurityUtils.getSubject();
Permission p = new WildcardPermission("printer:print:laserjet4400n");
if (currentUser.isPermitted(p) {
//顯示 打印 按鈕
} else {
//不顯示按鈕? 灰色嗎?
}
```
WildcardPermission 令牌形式和構成選項將在 Shiro 的 [Permission](6.1.%20Permissions%20%E6%9D%83%E9%99%90.md)文檔中深入討論
上面的字符串使用默認的 WildcardPermission 格式,實際上你可以創造并使用你自己的字符串格式,我們將在下面 Realm 授權章節討論如何這樣做。
基于字符串的權限有利的一面在于你不需要實現一個接口而且簡單的字符串也非常易讀,而不利的一面在于不保證類型安全,而且當你需要定義超出字符串表現能力之外的更復雜的行為時,你仍舊需要利用權限接口實現你自己的權限對象。實際上,大部分 Shiro 的終端用戶因為其簡單而選擇基于字符串的方式,但最終你的程序需求決定了哪一種方法會更好。
和基于對象的權限檢查方法一樣,下面是字符串權限檢查的函數:
Subject 方法 描述 [isPermitted(String perm)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermitted(java.lang.String)) 如果Subject被允許執行字符串表達的動作或資源訪問權限,返回真,否則返回假; [isPermitted(String... perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermitted(java.util.List)) 按照參數順序返回isPermitted的結果數組,當許多字符串權限需要檢查時非常有用(如定制一個復雜的視圖時); [isPermittedAll(String... perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isPermittedAll(java.lang.String...)) 當Subject具備所有字符串定義的權限時返回真,否則返回假。#### Permission Assertions 權限判斷
另一種檢查 Subject 是否被允許做某件事的方法是,在邏輯執行之前簡單判斷他們是否具備所需的權限,如果不允許,[AuthorizationException](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/AuthorizationException.html)異常被拋出,如果是允許的,判斷將安靜地執行并按期望順序執行下面的邏輯。
例如:
```
Subject currentUser = SecurityUtils.getSubject();
//擔保允許當前用戶
//開一個銀行帳戶:
Permission p = new AccountPermission("open");
currentUser.checkPermission(p);
openBankAccount();
```
或者,同樣的判斷,可以用字符串形式:
```
Subject currentUser = SecurityUtils.getSubject();
//擔保允許當前用戶
//開一個銀行帳戶:
currentUser.checkPermission("account:open");
openBankAccount();
```
與 isPermitted\* 方法相比較,這種方法的優勢是代碼更為清晰,如果當前Subject 不符合條件,你不必創建你自己的 AuthorizationExceptions 異常(如果你不想那么做)。
下面是你可以根據需要調用的函數:
Subject 方法 描述 [checkPermission(Permission p)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkPermission(org.apache.shiro.authz.Permission)) 如果Subject被允許執行特定權限實例指定的動作或資源訪問,安靜地返回,否則拋出AuthorizationException異常。 [checkPermission(String perm)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkPermission(java.lang.String)) 如果Subject被允許執行權限字符串指定的動作或資源訪問,安靜地返回,否則拋出AuthorizationException異常。 [checkPermissions(Collection perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkPermissions(java.util.Collection)) 如果Subject被允許執行所有權限實例指定的動作或資源訪問,安靜地返回,否則拋出AuthorizationException異常。 [checkPermissions(String... perms)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#checkPermissions(java.lang.String...)) 和上面的checkPermissions效果一樣,只是使用字符串權限類型。### Annotation-based Authorization 基于注解的授權
如果你更喜歡基于注解的授權控制,除了 Subject 的 API 之外,Shiro提供了一個 Java 5 的注解集。
#### Configuration 配置
在你使用 JAVA 的注解之前,你需要在程序中啟動 AOP 支持,因為有許多AOP 框架,所以很不幸,在這里并沒有標準的在程序中啟用 AOP 的方法。
關于AspectJ,你可以查看我們的[AspectJ sample application](http://svn.apache.org/repos/asf/shiro/trunk/samples/aspectj/);
關于Spring,你可以查看 [Spring Integration](../V.%20Integration%20%E6%95%B4%E5%90%88/15.%20Spring%20Framework.md)文檔;
關于Guice,你可以查看我們的 [Guice Integration](../V.%20Integration%20%E6%95%B4%E5%90%88/16.%20Guice.md)文檔;
#### RequiresAuthentication 注解
[RequiresAuthentication](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/annotation/RequiresAuthentication.html) 注解表示在訪問或調用被注解的類/實例/方法時,要求 Subject 在當前的 session中已經被驗證。
舉例:
```
@RequiresAuthentication
public void updateAccount(Account userAccount) {
//這個方法只會被調用在
//Subject 保證被認證的情況下
...
}
```
這基本上與下面的基于對象的邏輯效果相同:
```
public void updateAccount(Account userAccount) {
if (!SecurityUtils.getSubject().isAuthenticated()) {
throw new AuthorizationException(...);
}
//這里 Subject 保證被認證的情況下
...
}
```
#### RequiresGuest 注解
[RequiresGuest](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/annotation/RequiresGuest.html) 注解表示要求當前Subject是一個“guest(訪客)”,也就是,在訪問或調用被注解的類/實例/方法時,他們沒有被認證或者在被前一個Session 記住。
例如:
```
@RequiresGuest
public void signUp(User newUser) {
//這個方法只會被調用在
//Subject 未知/匿名的情況下
...
}
```
這基本上與下面的基于對象的邏輯效果相同:
```
public void signUp(User newUser) {
Subject currentUser = SecurityUtils.getSubject();
PrincipalCollection principals = currentUser.getPrincipals();
if (principals != null && !principals.isEmpty()) {
//已知的身份 - 不是 guest(訪客):
throw new AuthorizationException(...);
}
//在這里 Subject 確保是一個 'guest(訪客)'
...
}
```
#### RequiresPermissions 注解
[RequiresPermissions](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/annotation/RequiresPermissions.html) 注解表示要求當前Subject在執行被注解的方法時具備一個或多個對應的權限。
例如:
```
@RequiresPermissions("account:create")
public void createAccount(Account account) {
//這個方法只會被調用在
//Subject 允許創建一個 account 的情況下
...
}
```
這基本上與下面的基于對象的邏輯效果相同
```
public void createAccount(Account account) {
Subject currentUser = SecurityUtils.getSubject();
if (!subject.isPermitted("account:create")) {
throw new AuthorizationException(...);
}
//在這里 Subject 確保是允許
...
}
```
#### RequiresRoles 注解
[RequiresRoles](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/annotation/RequiresRoles.html) 注解表示要求當前Subject在執行被注解的方法時具備所有的角色,否則將拋出 AuthorizationException 異常。
例如:
```
@RequiresRoles("administrator")
public void deleteUser(User user) {
//這個方法只會被 administrator 調用
...
}
```
這基本上與下面的基于對象的邏輯效果相同
```
public void deleteUser(User user) {
Subject currentUser = SecurityUtils.getSubject();
if (!subject.hasRole("administrator")) {
throw new AuthorizationException(...);
}
//Subject 確保是一個 'administrator'
...
}
```
#### RequiresUser 注解
[RequiresUser](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/annotation/RequiresUser.html)\* 注解表示要求在訪問或調用被注解的類/實例/方法時,當前 Subject 是一個程序用戶,“程序用戶”是一個已知身份的 Subject,或者在當前 Session 中被驗證過或者在以前的 Session 中被記住過。
例如:
```
@RequiresUser
public void updateAccount(Account account) {
//這個方法只會被 'user' 調用
//i.e. Subject 是一個已知的身份with a known identity
...
}
```
這基本上與下面的基于對象的邏輯效果相同
```
public void updateAccount(Account account) {
Subject currentUser = SecurityUtils.getSubject();
PrincipalCollection principals = currentUser.getPrincipals();
if (principals == null || principals.isEmpty()) {
//無身份 - 他們是匿名的,不被允許
throw new AuthorizationException(...);
}
//Subject 確保是一個已知的身份
...
}
```
### JSP TagLib Authorization 標簽庫授權
Shiro 提供了一個標簽庫來控制 JSP/GSP 頁面輸出,這將在 [Web](../III.%20Web%20Applications/10.%20Web.md) 文檔中的 JSP/GSP 標簽庫中討論
## Authorization Sequence 授權序列
現在我們已經看到如何對當前 Subject 執行授權,讓我們了解一下當一個授權命令調用時 Shiro 內部發生了什么事情。
我們仍使用前面[Architecture](../I.%20Overview%20%E6%80%BB%E8%A7%88/3.%20Architecture%20%E6%9E%B6%E6%9E%84.md)章節里的架構圖,在左側僅僅與授權相關的組件是高亮的,每一個數字代表授權操作中的一個步驟:

**第1步:**程序或框架代碼調用一個 Subject 的`hasRole*`、`checkRole*`、 `isPermitted*`或者 `checkPermission*`方法,傳遞所需的權限或角色。
**第2步:**Subject實例,通常是一個 [DelegatingSubject](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/support/DelegatingSubject.html)(或子類),通過調用securityManager 與各 `hasRole*`、`checkRole*`、 `isPermitted*` 或 `checkPermission*` 基本一致的方法將權限或角色傳遞給程序的 SecurityManager(實現了 [org.apache.shiro.authz.Authorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Authorizer.html) 接口)
**第3步:**SecurityManager 作為一個基本的“保護傘”組件,接替/代表其內部 [org.apache.shiro.authz.Authorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Authorizer.html) 實例通過調用 authorizer 的各自的 `hasRole*`, `checkRole*` , `isPermitted*` ,或 `checkPermission*` 方法。 authorizer 默認情況下是一個實例 [ModularRealmAuthorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/ModularRealmAuthorizer.html) 支持協調一個或多個實例 Realm 在任何授權操作實例。
**第4步:**,檢查每一個被配置的 Realm 是否實現相同的 [Authorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Authorizer.html)接口,如果是,Realm 自己的各 `hasRole*`、`checkRole*`、 `isPermitted*` 或 `checkPermission*` 方法被調用。
### ModularRealmAuthorizer
前面提到過,Shiro SecurityManager 默認使用 [ModularRealmAuthorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/ModularRealmAuthorizer.html) 實例,ModularRealmAuthorizer 實例同等支持用一個 Realm 的程序和用多個 Realm 的程序。
對于任何授權操作,ModularRealmAuthorizer 將在其內部的 Realm 集中迭代(iterator),按迭代(iteration)順序同每一個 Realm 交互,與每一個 Realm 交互的方法如下:
1\.如果Realm實現了 [Authorizer](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Authorizer.html) 接口,調用它各自的授權方法(`hasRole*`、 `checkRole*`、`isPermitted*`或 `checkPermission*`)。
1\.1.如果 Realm 函數的結果是一個 exception,該 exception 衍生自一個 Subject 調用者的 AuthorizationException,就切斷授權過程,剩余的授權 Realm 將不在執行。
1\.2.如果 Realm 的方法是一個 `hasRole*` 或 `isPermitted*`,并且返回真,則真值立即被返回而且剩余的 Realm 被短路,這種做法作為一種性能增強,在一個 Realm 判斷允許后,隱含認為這個 Subject 被允許。它支持最安全的安全策略:默認情況下所有都被禁止,明確指定允許的事情。
2\.如果 Realm 沒有實現 Authorizer 接口,將被忽略。
### Realm Authorization Order 授權順序
需要指出非常重要的一點,就如同驗證(authentication)一樣,ModularRealmAuthorizer 按迭代(iteration)順序與 Realm 交互。
ModularRealmAuthorizer 擁有 SecurityManager 配置的 Realm 實例的入口,當執行一個授權操作時,它將在整個集合中進行迭代(iteration),對于每一個實現 Authorizer 接口的 Realm,調用Realm 各自的 Authorizer 方法(如 hasRole*、 checkRole*、 isPermitted*或 checkPermission*)。
### Configuring a global PermissionResolver 配置全局的 PermissionResolver
當執行一個基于字符串的權限檢查時,大部分 Shiro 默認的 Realm 將會在執行[權限](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/Permission.html)隱含邏輯之前首先把這個字符串轉換成一個常用的權限實例。
這是因為權限被認為是基于隱含邏輯而不是相等檢查(查看[Permission](https://github.com/waylau/apache-shiro-1.2.x-reference/blob/master/II.%20Core%20%E6%A0%B8%E5%BF%83/6.1.%20Permissions%20%E6%9D%83%E9%99%90.md)章節了解更多隱含與相等的對比)。隱含邏輯用代碼表示要比通過字符串對比好,因此,大部分 Realm需要轉換一個提交的權限字符串為對應的權限實例。
為了這個轉換目的,Shiro 支持 [PermissionResolver](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/PermissionResolver.html),大部分 Shiro Realm 使用 PermissionResolver 來支持它們對Authorizer 接口中基于字符串權限方法的實現:當這些方法在Realm上被調用時,將使用PermissionResolver 將字符串轉換為權限實例,并執行檢查。
所有的 Shiro Realm 默認使用內部的 [WildcardPermissionResolver](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/WildcardPermissionResolver.html),它使用 Shiro 的[WildcardPermission](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/WildcardPermission.html)字符串格式。
如果你想創建你自己的 PermissionResolver 實現,比如說你想創建你自己的權限字符串語法,希望所有配置的Realm實例都支持這個語法,你可以把自己的 PermissionResolver 設置成全局,供所有 realm 使用。
如,在shiro.ini中:
```
globalPermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.permissionResolver = $globalPermissionResolver
...
```
*PermissionResolverAware*
*如果你想配置一個全局的 PermissionResolver,每一個會讀取這個PermissionResolver 配置的 Realm 必須實現[PermissionResolverAware](http://shiro.apache.org/static/current/apidocs/src-html/org/apache/shiro/authz/permission/PermissionResolverAware.html) 接口,這確保被配置 PermissionResolver 的實例可以傳遞給支持這種配置的每一個 Realm。*
如果你不想使用一個全局的 PermissionResolver 或者你不想被PermissionResolverAware 接口麻煩,你可以明確地為單個的 Realm 配置 PermissionResolver 接口(可看作是JavaBean的setPermissionResolver 方法):
```
permissionResolver = com.foo.bar.authz.MyPermissionResolver
realm = com.foo.bar.realm.MyCustomRealm
realm.permissionResolver = $permissionResolver
...
```
### Configuring a global RolePermissionResolver 配置全局的RolePermissionResolver
與 PermissionResolver 類似,[RolePermissionResolver](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/RolePermissionResolver.html) 有能力表示執行權限檢查的 Realm 所需的權限實例。
最主要的不同在于接收的字符串是一個角色名,而不是一個權限字符串。
RolePermissionResolver 被 Realm 在需要時用來轉換一個角色名到一組明確的權限實例。
這是非常有用的,它支持那些遺留的或者不靈活的沒有權限概念的數據源。
例如,許多 LDAP 目錄存儲角色名稱(或組名)但不支持角色名和權限的聯合,因為它沒有權限的概念。一個使用 shiro 的程序可以使用存儲于 LDAP 的角色名,但需要實現一個 RolePermissionResolver 來轉換 LDAP 名到一組確切的權限中以執行明確的訪問控制,權限的聯合將被存儲于其它的數據存儲中,比如說本地數據庫。
因為這種將角色名轉換為權限的概念是特定的,Shiro 默認的 Realm 沒有使用它們。
然而,如果你想創建你自己的 RolePermissionResolver 并且希望用它配置多個 Realm 實現,你可以將你的 RolePermissionResolver設置成全局。
shiro.ini
```
globalRolePermissionResolver = com.foo.bar.authz.MyPermissionResolver
...
securityManager.authorizer.rolePermissionResolver = $globalRolePermissionResolver
...
```
*RolePermissionResolverAware*
*如果你想配置一個全局的 RolePermissionResolver, 每個 Realm 接收必須實現了 [RolePermisionResolverAware](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/permission/RolePermissionResolverAware.html) 接口的配置了的 RolePermissionResolver 。這保證了配置全局 RolePermissionResolver 實例可以傳遞到各個支持這樣配置的 Realm 。*
如果你不想使用全局的 RolePermissionResolver 或者你不想麻煩實現 RolePermissionResolverAware 接口,你可以單獨為一個 Realm 配置 RolePermissionResolver(可以看作 JavaBean 的 setRolePermissionResolver 方法)。
```
rolePermissionResolver = com.foo.bar.authz.MyRolePermissionResolver
realm = com.foo.bar.realm.MyCustomRealm
realm.rolePermissionResolver = $rolePermissionResolver
...
```
### Custom Authorizer 定制Authorizer
如果你的程序使用多于一個 Realm 來執行授權而 ModularRealmAuthorizer 默認的簡單迭代(iteration)、短路授權的行為不能滿足你的需求,你可以創建自己的 Authorizer 并配置給相應的 SecurityManager。
例如,在shiro.ini中:
```
[main]
...
authorizer = com.foo.bar.authz.CustomAuthorizer
securityManager.authorizer = $authorizer
```
- Introduction
- 1. Introduction 介紹
- 2. Tutorial 教程
- 3. Architecture 架構
- 4. Configuration 配置
- 5. Authentication 認證
- 6. Authorization 授權
- 6.1. Permissions 權限
- 7. Realms
- 8. Session Management
- 9. Cryptography 密碼
- 10. Web
- 10.1. Configuration 配置
- 10.2. 基于路徑的 url 安全
- 10.3. Default Filters 默認過濾器
- 10.4. Session Management
- 10.5. JSP Tag Library
- 11. Caching 緩存
- 12. Concurrency & Multithreading 并發與多線程
- 13. Testing 測試
- 14. Custom Subjects 自定義 Subject
- 15. Spring Framework
- 16. Guice
- 17. CAS
- 18. Command Line Hasher
- 19. Terminology 術語
- 20. 10 Minute Tutorial 十分鐘教程
- 21. Beginner's Webapp Tutorial 初學者web應用教程
- 22. Application Security With Apache Shiro 用Shiro保護你的應用安全
- 23. CacheManager 緩存管理
- 24. Apache Shiro Cryptography Features 加密功能