# 5. Authentication 認證
# 5. Authentication 認證
認證(Authentication):身份驗證的過程--也就是證明一個用戶的真實身份。為了證明用戶身份,需要提供系統理解和相信的身份信息和證據。
需要通過向 Shiro 提供用戶的身份(principals)和證明(credentials )來判定是否和系統所要求的匹配。
- **Principals(身份)** 是Subject的“標識屬性”,可以是任何與Subject相關的標識,比如說名稱(給定名稱)、名字(姓或者昵稱)、用戶名、安全號碼等等,當然像昵稱這樣的內容不能很好的對Subject進行獨特標識,所以最好的身份信息(Principals)是使用在程序中唯一的標識--典型的使用用戶名或郵件地址。
- **Primary Principal(最主要的身份)**雖然 Shiro 可以使用任何數量的身份,Shiro 還是希望一個程序精確地使用一個主要的身份--一個僅有的唯一標識 Subject 值。在多數程序中經常會是一個用戶名、郵件地址或者全局唯一的用戶 ID。
- **Credentials(證明)** 通常是只有 Subject 知道的機密內容,用來證明他們真正擁有所需的身份,一些簡單的證書例子如密碼、指紋、眼底掃描和X.509證書等。
最常見的身份/證明是用戶名和密碼,用戶名是所需的身份說明,密碼是證明身份的證據。如果一個提交的密碼和系統要求的一致,程序就認為該用戶身份正確,因為其他人不應該知道同樣的密碼。
## Authenticating Subjects
Subject 驗證的過程可以有效地劃分分以下三個步驟:
1\.收集 Subject 提交的身份和證明;
2\.向 Authentication 提交身份和證明;
3\.如果提交的內容正確,允許訪問,否則重新嘗試驗證或阻止訪問
下面的代碼示范了 Shiro API 如何實現這些步驟:
### 第一步:收集用戶身份和證明
```
//最常用的情況是 username/password 對:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//”Remember Me” 功能是內建的
token.setRememberMe(true);
```
在這里我們使用 [UsernamePasswordToken](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/UsernamePasswordToken.html),支持所有常用的用戶名/密碼驗證途徑,這是一個 [org.apache.shiro.authc.AuthenticationToken](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/AuthenticationToken.html) 接口的實現,這個接口被 Shiro 認證系統用來提交身份和證明。
注意 Shiro 并不關心你如何獲取這些信息:也許是用戶從一個HTML表單中提交的,或者可能從一個 HTTP 請求字串中解析的,也可能來自于Swing或者 Flex GUI 的密碼表單,或者通過命令行參數得到。從程序終端用戶獲取信息的過程與 Shiro 的 AuthenticationToken 完全無關。
你可以隨自己喜歡構造和引用 AuthenticationToken 實例 -- 這是協議無關的。
這個例子同樣顯示我們希望 Shiro 在嘗試驗證時執行 “Remember Me” 服務,這確保 Shiro 在用戶今后返回系統時能記住他們的身份,我們會在以后的章節討論 [“Remember Me”](https://cwiki.apache.org/confluence/pages/createpage.action?spaceKey=SHIRO&title=Remember+Me&linkCreation=true&fromPageId=25203054) 服務。
### 第二步:提交身份和證明
當身份和證明住處被收集并實例化為一個 AuthenticationToken (認證令牌)后,我們需要向 Shiro 提交令牌以執行真正的驗證嘗試:
```
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
```
在獲取當前執行的 Subject 后,我們執行一個單獨的 [login](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#login(org.apache.shiro.authc.AuthenticationToken)) 命令,將之前創建的 AuthenticationToken 實例傳給它。
調用 login 方法將有效地執行身份驗證。
### 三步:處理成功或失敗
當login函數沒有返回信息時表明驗證通過了。程序可以繼續運行,此時執行 SecurityUtils.getSubject() 將返回驗證后的 Subject 實例,subject.[isAuthenticated()](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isAuthenticated()) 將返回true。
但是如果 login 失敗了呢?例如,用戶提供了一個錯誤的密碼或者因訪問系統次數過多而被鎖定將會怎樣呢?
Shiro擁有豐富的運行期異常[AuthenticationException](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authz/AuthorizationException.html)可以精確標明為何驗證失敗,你可以將 login 放入到 try/catch 塊中并捕獲所有你想捕獲的異常并對它們做出處理。例如:
```
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... 捕獲你自己的異常 ...
} catch ( AuthenticationException ae ) {
//未預計的錯誤?
}
//沒問題,繼續
```
如果原有的異常不能滿足你的需求,可以創建自定義的AuthenticationExceptions 來表示特定的失敗場景。
*登錄失敗小貼士*
*雖然你的代碼可以對指定的異常做出處理并執行某些所需的邏輯,但有經驗的安全做法是僅向終端用戶輸出一般的失敗信息,例如“錯誤的用戶名和密碼”。這確保不向嘗試攻擊你的黑客提供有用的信息。*
## Remembered vs. Authenticated
如上例所示,Shiro 支持在登錄過程中執行"remember me",在此值得指出,一個已記住的 Subject(remembered Subject)和一個正常通過認證的 Subject(authenticated Subject)在 Shiro 是完全不同的。
- **記住的(Remembered)**:一個被記住的 Subject 不不會是匿名的,擁有一個已知的身份(也就是說subject.[getPrincipals()](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#getPrincipals())返回非空)。它的身份被先前的認證過程所記住,并存于先前session中,一個被認為記住的對象在執行subject.[isRemembered()](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isRemembered())返回true。
- **已驗證(Authenticated)**:一個被驗證的 Subject 是成功驗證后(如登錄成功)并存于當前 session 中,一個被認為驗證過的對象調用subject.[isAuthenticated()](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/Subject.html#isAuthenticated()) 將返回true。
*互斥的*
*已記住(Remembered)和已驗證(Authenticated)是互斥的--一個標識值為真另一個就為假,反過來也一樣。*
### Why the distinction?為什么區分?
驗證(authentication)有明顯的證明含義,也就是說,需要擔保Subject 已經被證明他們是誰。
當一個用戶僅僅在上一次與程序交互時被記住,證明的狀態已經不存在了:被記住的身份只是給系統一個信息這個用戶可能是誰,但不確定,沒有辦法擔保這個被記住的 Subject 是所要求的用戶,一旦這個 Subject 被驗證通過,他們將不再被認為是記住的因為他們的身份已經被驗證并存于當前的session中。
所以盡管程序大部分情況下仍可以針對記住的身份執行用戶特定的邏輯,比如說自定義的視圖,但不要執行敏感的操作直到用戶成功執行身份驗證使其身份得到確定。
例如,檢查一個 Subject 是否可以訪問金融信息應該取決于是否被驗證(isAuthenticated())而不是被記住(isRemembered()),要確保該Subject 是所需的和通過身份驗證的。
### An illustrating example 一個例子說明
下面是一個非常常見的場景幫助說明被記住和被驗證之間差別為何重要。
假設你使用[Amazon.com](http://www.amazon.com/),你已經成功登錄并且在購物籃中添加了一些書籍,但你由于臨時要參加一個會議,匆忙中你忘記退出登錄,當會議結束,回家的時間到了,于是你離開了辦公室。
第二天當你回到工作,你意識到你沒有完成你的購買動作,于是你回到Amazon.com,這時,Amazon.com“記得”是是,通過你的名字向你打招呼,仍舊給你提供個性化的圖書推薦,對于Amazon.com,subject.isRemembered() 將返回真。
但是當你想訪問你帳號的信用卡信息完成圖書購買的時候會怎樣呢?雖然Amazon.com “記住”了你(isRemembered() == true),它不能擔保你就是你(也許是正在使用你計算機的同事)。
于是,在你執行像使用信用卡信息之類的敏感操作之前,Amazon.com 強制你登錄以使他們擔保你的身份,在你登錄之后,你的身份已經被驗證,對于Amazon.com,isAuthenticated()將返回真。
這類情景經常發生,所以 Shiro 加入了該功能,你可以在你的程序中使用。現在是使用 isRemembered() 還是使用 isAuthenticated() 來定制你的視圖和工作流完全取決于你自己,但 Shiro 維護這種狀態基礎以防你可能會需要。
## Logging Out 退出登錄
與驗證相對的是釋放所有已知的身份信息,當 Subject 與程序不再交互了,你可以調用 subject.logout() 丟掉所有身份信息。
```
currentUser.logout(); //清除驗證信息,使 session 失效
```
當你調用 logout,任何現存的 session 將變為不可用并且所有的身份信息將消失(如:在 web 程序中,RememberMe 的 Cookie 信息同樣被刪除)。
當一個 Subject 退出登錄,Subject 被重新認定為匿名的,對于 web 程序,如果需要可以重新登錄。
*Web 程序需注意*
*因為在 Web 程序中記住身份信息往往使用 Cookies,而 Cookies 只能在 Response 提交時才能被刪除,所以強烈要求在為最終用戶調用subject.logout() 之后立即將用戶引導到一個新頁面,確保任何與安全相關的 Cookies 如期刪除,這是 Http 本身 Cookies 功能的限制而不是 Shiro 的限制。*
## Authentication Sequence 認證序列
直到現在,我們只看到如何在程序代碼中驗證一個 Suject,現在我們看一下當一個驗證發生時 Shiro 內部發生了什么。
我們仍使用之前在架構[Architecture](../I.%20Overview%20%E6%80%BB%E8%A7%88/3.%20Architecture%20%E6%9E%B6%E6%9E%84.md)章節里見到過的架構圖,僅將左側跟認證相關的組件高亮,每一個數字代表認證中的一個步驟:

**第1步**:程序代碼調用 Subject.login 方法,向AuthenticationToken(認證令牌)實例的構造函數傳遞用戶的身份和證明。
**第2步**:Subject 實例,通常是一個 [DelegatingSubject](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/subject/support/DelegatingSubject.html)(或其子類)通過調用 securityManager.login(token )將這個令牌轉交給程序的 SecurityManager。
**第3步**:SecurityManager,基本的“安全傘”組件,得到令牌并通過調用 authenticator.[authenticate(token)](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/Authenticator.html#authenticate(org.apache.shiro.authc.AuthenticationToken))簡單地將其轉交它內部的 [Authenticator](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/Authenticator.html) 實例,大部分情況下是一個 [ModularRealmAuthenticator](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/pam/ModularRealmAuthenticator.html) 實例,用來支持在驗證過程中協調一個或多個Realm實例。ModularRealmAuthenticator 本質上為 Apache Shiro(在 PAM 術語中每一個 Realm 稱為一個“模塊”)提供一個 [PAM](http://en.wikipedia.org/wiki/Pluggable_Authentication_Modules) 類型的范例。
**第4步**:如程序配置了多個 Realm,ModularRealmAuthenticator實例將使用其配置的 [AuthenticationStrategy](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/pam/AuthenticationStrategy.html) 開始一個 多 Realm 身份驗證的嘗試。在 Realm 被驗證調用的整個過程中,AuthenticationStrategy(安全策略)被調用用來回應每個Realm結果,我們將稍后討論 AuthenticationStrategies。
*注意:單 Realm 程序*
*如果僅有一個 Realm 被配置,它直接被調用--在單 Realm 程序中不需要AuthenticationStrategy。*
**第5步**:每一個配置的 Realm 都被檢驗看其是否[支持](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/realm/Realm.html#supports(org.apache.shiro.authc.AuthenticationToken))提交的AuthenticationToken,如果支持,則該 Realm 的 [getAuthenticationInfo](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/realm/Realm.html#getAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)) 方法隨著提交的牌被調用,getAuthenticationInfo 方法為特定的 Realm 有效提供一次獨立的驗證嘗試,我們將稍后討論 Realm 驗證行為。
### Authenticator
就像以前提到過的,Shiro SecurityManager implementations默認使用一個 [ModularRealmAuthenticator](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/pam/ModularRealmAuthenticator.html) 實例, ModularRealmAuthenticator 同樣支持單 Realm 和多 Realm。
在一個單 Realm 程序中,ModularRealmAuthenticator 將直接執行單獨的 Realm,如果配置有兩個或以上 Realm,將會使用AuthenticationStrategy 實例來協調如何進行驗證,我們將在下面的章節中討論 AuthenticationStrategy。
如果你希望用自定義的 Authenticator 實現配置 SecurityManager,你可以在 shiro.ini 中做這件事,如:
```
[main]
...
authenticator = com.foo.bar.CustomAuthenticator
securityManager.authenticator = $authenticator
```
盡管在實際操作中, ModularRealmAuthenticator 適用于大部分需求。
### AuthenticationStrategy
當一個程序中定義了兩個或多個 realm 時,ModularRealmAuthenticator 使用一個內部的[AuthenticationStrategy](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/pam/AuthenticationStrategy.html) 組件來決定一個驗證是否成功。
例如,如果一個 Realm 驗證一個 AuthenticationToken 成功,但其他的都失敗了,那這次嘗試是否被認為是成功的呢?是不是所有 Realm 驗證都成功了才認為是成功?又或者一個 Realm 驗證成功,是否還有必要討論其他Realm?AuthenticationStrategy 根據程序需求做出恰當的決定。
AuthenticationStrategy 是一個 stateless 的組件,在整個驗證過程中在被用到4次(在這4次活動中需要必要的 state 時,state 將作為方法參數傳遞)
1\.在任何 Realms 被執行之前;
2\.在某個的 Realm 的 getAuthenticationInfo 方法調用之前;
3\.在某個的 Realm 的 getAuthenticationInfo 方法調用之后;
4\.在所有的 Realm 被執行之后。
AuthenticationStrategy 還有責任從每一個成功的 Realm 中收集結果并將它們“綁定”到一個單獨的 [AuthenticationInfo](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/AuthenticationInfo.html),這個AuthenticationInfo 實例是被 Authenticator 實例返回的,并且 shiro 用它來展現一個 Subject 的最終身份(也就是 Principals )。
*Subject 身份“展示(view)”*
*如果你在程序中使用多于一個的 Realm 從多個數據源中獲取帳戶數據,程序可看到的是 AuthenticationStrategy 最終負責 Subject 身份最終“合并(merged)”的視圖*
Shiro 有3個具體的 AuthenticationStrategy 實現:
AuthenticationStrategy class Description [AtLeastOneSuccessfulStrategy](static/current/apidocs/org/apache/shiro/authc/pam/AtLeastOneSuccessfulStrategy.html) 如果有一個或多個Realm驗證成功,所有的嘗試都被認為是成功的,如果沒有一個驗證成功,則該次嘗試失敗 [FirstSuccessfulStrategy](static/current/apidocs/org/apache/shiro/authc/pam/FirstSuccessfulStrategy.html) 只有從第一個成功驗證的Realm返回的信息會被使用,以后的Realm將被忽略,如果沒有一個驗證成功,則該次嘗試失敗 [AllSuccessfulStrategy](static/current/apidocs/org/apache/shiro/authc/pam/AllSuccessfulStrategy.html) 所有配置的Realm在全部嘗試中都成功驗證才被認為是成功,如果有一個驗證不成功,則該次嘗試失敗。 ModularRealmAuthenticator 默認使用 AtLeastOneSuccessfulStrategy 實現,這也是最常用的策略,然而你也可以配置你希望的不同的策略。
shiro.ini
```
[main]
...
authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy = $authcStrategy
...
```
*自定義的 AuthenticationStrategy*
*如果你希望創建你自己的 AuthenticationStrategy 實現,你可以使用 [org.apache.shiro.authc.pam.AbstractAuthenticationStrategy](http://shiro.apache.org/static/current/apidocs/org/apache/shiro/authc/pam/AbstractAuthenticationStrategy.html) 作為起始點。AbstractAuthenticationStrategy 類自動實現 '綁定(bundling)'/聚集(aggregation)行為將來自于每個Realm 的結果收集到一個 AuthenticationInfo 實例中。*
### Realm 驗證的順序
非常重要的一點是,和 Realm 交互的 ModularRealmAuthenticator 按迭代(iteration)順序執行。
ModularRealmAuthenticator 可以訪問為 SecurityManager 配置的 Realm 實例,當嘗試一次驗證時,它將在集合中遍歷,支持對提交的 AuthenticationToken 處理的每個 Realm 都將執行 Realm 的 getAuthenticationInfo 方法。
#### 隱含的順序
在使用 ShiroINI 配置文件形式時,你可以按你希望其處理 AuthenticationToken 的順序來配置 Realm,例如,在shiro.ni 中,Realm 將按照他們在INI文件中定義的順序執行。
```
blahRealm = com.company.blah.Realm
...
fooRealm = com.company.foo.Realm
...
barRealm = com.company.another.Realm
```
SecurityManager上配置了這三個 Realm,在一個驗證過程中,blahRealm, fooRealm, 和 barRealm 將被順序執行。
這基本上與定義下面這一行語句的效果相同:
```
securityManager.realms = $blahRealm, $fooRealm, $barRealm
```
使用這種方法,你不需要設置 securityManager 的 realm 順序,每一個被定義的realm 將自動加入到 realms 屬性中。
#### Explicit Ordering明確的順序
如果你希望明確定義 realm 執行的順序,不管他們如何被定義,你可以設置 SecurityManager 的 realms 屬性,例如,使用上面定義的 realm,但你希望 blahRealm 最后執行而不是第一個:
```
blahRealm = com.company.blah.Realm
...
fooRealm = com.company.foo.Realm
...
barRealm = com.company.another.Realm
securityManager.realms = $fooRealm, $barRealm, $blahRealm
```
*明確 Realm 包含*
*當你明確的配置 securityManager.realms 屬性時,只有被引用的 realm 將為 SecurityManager 配置,也就是說你可能在 INI 中定義了5個 realm,但實際上只使用了3個,如果在 realm 屬性中只引用了3個,這和隱含的 realm 順序不同,在那種情況下,所有有效的 realm 都會用到。*
## Realm Authentication 驗證
本章闡述了當一個驗證嘗試發生時 Shiro 主要的工作流程,而在驗證過程中,用到的 Realm 內產生的工作流程(如上面提到的第5步)將在 [Realm](7.%20Realms.md) 章中 Realm Authentication 節討論。
## 為文檔加把手
我們希望這篇文檔可以幫助你使用 Apache Shiro 進行工作,社區一直在不斷地完善和擴展文檔,如果你希望幫助 Shiro 項目,請在你認為需要的地方考慮更正、擴展或添加文檔,你提供的任何點滴幫助都將擴充社區并且提升 Shiro。
提供你的文檔的最簡單的途徑是將它發送到用戶[論壇](http://shiro-user.582556.n2.nabble.com/)或[郵件列表](http://shiro.apache.org/mailing-lists.html)
*譯者注:*如果對本中文翻譯有疑議的或發現勘誤歡迎指正,[點此](https://github.com/waylau/apache-shiro-1.2.x-reference/issues)提問。
- 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 加密功能