天天看點

shiro實戰系列(十)之Subject

毫無疑問,在 Apache Shiro 中最重要的概念就是 Subject。'Subject'僅僅是一個安全術語,是指應用程式使用者的特定 安全的“視圖”。一個 Shiro Subject 執行個體代表了一個單一應用程式使用者的安全狀态和操作。  

這些操作包括:

authentication(login)

authorization(access control)

session access

logout  

我們原本希望把它稱為"User"由于這樣“很有意義”,但是我們決定不這樣做:太多的應用程式現有的 API 已經有 自己的 User classes/frameworks,我們不希望和這些起沖突。此外,在安全領域,"Subject"這一詞實際上是公認的術 語。 

 Shiro 的 API 為應用程式提供 Subject 為中心的程式設計範式支援。當編碼應用程式邏輯時,大多數應用程式開發人員想 知道誰才是目前正在執行的使用者。雖然應用程式通常能夠通過它們自己的機制(UserService 等)來查找任何使用者, 但涉及到安全性時,最重要的問題是“誰才是目前的使用者?”。

雖然通過使用SecurityManager可以捕獲任何Subject,但隻有基于目前使用者/Subject的應用程式代碼更自然,更直覺。

The Currently Executing Subject(目前執行的 Subject)

幾乎在所有環境下,你能夠獲得目前執行的 Subject 通過使用 org.apache.shiro.SecurityUtils:  

shiro實戰系列(十)之Subject

getSubject()方法調用一個獨立的應用程式,該應用程式可以傳回一個在應用程式特有位置上基于使用者資料的 Subject, 在伺服器環境中(如,Web 應用程式),它基于與目前線程或傳入的請求相關的使用者資料上獲得 Subject。  

當你獲得了目前的 Subject 後,你能夠拿它做些什麼?  

如果你想在他們目前的 session 中使事情對使用者變得可用,你可得的他們的 session:

shiro實戰系列(十)之Subject

Session 是一個 Shiro 的具體執行個體,它提供了大多數你經常要和 HttpSessions 用到的東西,但有一些額外的好處和一 個很大的差別:它不需要一個 HTTP 環境!   如果在 Web 應用程式内部部署,預設的 Session 将會是基于 HttpSession 的。但是,在一個非 Web 環境中,像這個 簡單的 Quickstart,Shiro 将會預設自動地使用它的 Enterprise Session Management。這意味着你可以在你的應用程式 中使用相同的 API,在任何層,無論部署環境。這打開了應用程式的全新世界,由于任何需要 session 的應用程式不 再被強迫使用 HttpSession 或 EJB Stateful Session Beans。而且,任何用戶端技術現在能夠共享會話資料。   是以,你現在可以擷取一個 Subject 以及他們的 Session。對于真正有用的東西像檢查會怎麼樣呢,如果他們被允許 做某些事——如對角色和權限的檢查?   嗯,我隻能對已知的使用者做這些檢查。我們的 Subject 執行個體代表了目前的使用者,但誰又是實際上的目前使用者呢?呃, 他們都是匿名的——也就是說,直到他們至少登入一次。那麼,讓我們像下面這樣做:

shiro實戰系列(十)之Subject

那就是了!它再簡單不過了。   但如果他們的登入嘗試失敗了會怎麼樣?你可以捕獲各種各樣的具體的異常來告訴你到底發生了什麼:

shiro實戰系列(十)之Subject

你,作為應用程式/GUI 開發人員,可以基于異常選擇是否顯示消息給終端使用者(例如,“在系統中沒有與該使用者名 對應的帳戶。”)。有許多不同種類的異常供你檢查,或者你可以抛出你自己自定義的異常,這些異常可能是 Shiro 還未提供的。有關詳情,請檢視 AuthenticationException 的 JavaDoc。   好了,現在,我們有了一個登入的使用者,我們還有什麼可以做的呢?   比方說,他們是誰:

shiro實戰系列(十)之Subject
shiro實戰系列(十)之Subject

最後,當使用者完成了對應用程式的使用時,他們可以登出:

shiro實戰系列(十)之Subject

這個簡單的 API 包含了 90%的 Shiro 終端使用者在使用 Shiro 時将會處理的東西。

Custom Subject Instances(自定義 Subject 執行個體) Shiro 1.0 中添加了一個新特性,能夠在特殊情況下構造自定義/臨時的 subject 執行個體。  

Special Use Only!  你應該總是通過調用 SubjectUtils.getSubject()來獲得目前正在執行的 Subject;  建立自定義的 Subject 執行個體隻應在特殊情況下進行。

  當一些“特殊情況”是,這是可以很有用的: 系統啟動/引導——當沒有使用者月系統互動時,代碼應該作為一個'system'或 daemon 使用者來執行。建立 Subject 執行個體來代表一個特定的使用者是值得的,這樣引導代碼能夠以該使用者(如 admin)來執行。 鼓勵這種做法是由于它能保證 utility/system 代碼作為一個普通使用者以同樣的方式執行,以確定代碼是一緻的。 這使得代碼更易于維護,因為你不必擔心 system/daemon 方案的自定義代碼塊。 內建測試——你可能想建立Subject執行個體,在必要時可以在內建測試中使用。請參閱測試文檔擷取更多的内容。 Daemon/background 程序的工作——當一個 daemon 或 background 程序執行時,它可能需要作為一個特定的 使用者來執行。  

shiro實戰系列(十)之Subject

好了,假設你仍然需要建立自定義的 Subject 執行個體的情況下,讓我們看看如何來做:

Subject.Builder

Subject.Builder 被制定得非常容易建立 Subject 執行個體,而無需知道構造細節。   Builder 最簡單的用法是構造一個匿名的,session-less 的執行個體。

shiro實戰系列(十)之Subject

上面所展示的預設的 Subject.Builder 無參構造函數将通過 SecurityUtils.getSubject()方法使用應用程式目前可通路的 SecurityManager。你也可以指定被額外的構造函數使用的 SecurityManager 執行個體,如果你需要的話:

shiro實戰系列(十)之Subject

所有其他的 Subject.Builder 方法可以在 buildSubject()方法之前被調用,它們來提供關于如何構造 Subject 執行個體的上下 文。例如,假如你擁有一個 session ID ,想取得“擁有”該 session 的 Subject(假設該 session 存在且未過期):

shiro實戰系列(十)之Subject

同樣地,如你想建立一個 Subject 執行個體來反映一個确定的身份:

shiro實戰系列(十)之Subject

然後,你可以使用構造的 Subject 執行個體,如預期一樣對它進行調用。但請注意:   構造的 Subject 執行個體不會由于應用程式(線程)的進一步使用而自動地綁定到應用程式(線程)。如果你想讓它對 于任何代碼都能夠友善地調用 SecurityUtils.getSubject(),你必須確定建立好的 Subject 有一個線程與之關聯。

Thread Association(線程關聯) 如上所述,隻是建構一個 Subject 執行個體,并不與一個線程相關聯——一個普通的必要條件是線上程執行期間任何對 SecurityUtils.getSubject()的調用是否能正常工作。確定一個線程與一個 Subject 關聯有三種途徑:

(1) Automatic Association(自動關聯)—— 通過 Sujbect.execute*方法執行一個 Callable 或 Runnable 方法會自動地綁 定和解除綁定到線程的 Subject,在 Callable/Runnable 異常的前後。

(2) Manual Association(手動關聯)——你可以在目前執行的線程中手動地對 Subject 執行個體進行綁定和解除綁定。這 通常對架構開發人員非常有用。

(3) Different Thread(不同的線程)——通過調用 Subject.associateWith*方法将 Callable 或 Runnable 方法關聯到 Subject,然後傳回的 Callable/Runnable 方法在另一個線程中被執行。如果你需要為 Subject 在另一個線程上執 行工作的話,這是首選的方法。

了解線程關聯最重要的是,兩件事情必須始終發生:

1. Subject 綁定到線程,是以它線上程的所有執行點都是可用的。Shiro 做到這點通過它的 ThreadState 機制,該 機制是在 ThreadLocal 上的一個抽象。

2. Subject 将在某點解除綁定,即使線程的執行結果是錯誤的。這将確定線程保持幹淨,并在 pooled/reusable 線 程環境中清除任何之前的 Subject 狀态。   這些原則保證在上述三個機制中發生。接下來闡述它們的用法。  

Automatic Association(自動關聯)

如果你隻需要一個 Subject 暫時與目前的線程相關聯,同時你希望線程綁定和清理自動發生,Subject 的 Callable 或 Runnable 的直接執行正是你所需要的。在 Subject.execute 調用傳回後,目前線程被保證目前狀态與執行前的狀态是 一樣的。這個機制是這三個中使用最廣泛的。 

 例如,讓我們假定你有一些邏輯在系統啟動時需要執行。你希望作為一個特定使用者執行代碼塊,但一旦邏輯完成後, 你想確定線程/環境自動地恢複到正常。你可以通過調用 Subject.execute*方法來做到:  

shiro實戰系列(十)之Subject

這種方法在架構開發中也是很有用的。例如,Shiro 對 secure Spring remoting 的支援確定了遠端調用能夠作為一個特 定的 Subject 來執行:  

shiro實戰系列(十)之Subject

Manual Association(手動關聯)

雖然 Subject.execute*方法能夠在它們傳回後自動地清理線程的狀态,但有可能在一些情況下,你想自己管理 ThreadState。當結合 w/Shiro 時,這幾乎總是在架構開發層次使用,但它很少在 bootstrap/daemon 情景下使用(上 面 Subject.execute(callable)例子使用得更為頻繁)。  

shiro實戰系列(十)之Subject

最好的做法是在 try/finally 塊保證清理:

shiro實戰系列(十)之Subject

有趣的是,這正是 Subject.execute*方法實際上所做的——它們隻是在 Callable 或 Runnable 執行前後自動地執行這個 邏輯。Shiro 的 ShiroFilter 為 Web 應用程式執行幾乎相同的邏輯(ShiroFilter 使用 Web 特定的 ThreadState 的實作, 超出了本節的範圍)。

shiro實戰系列(十)之Subject

A Different Thread

如果你有一個 Callable 或 Runnable 執行個體要以 Subject 來執行,你将自己執行 Callable 或 Runnable(或這将它移交給 線程池或執行者或 ExcutorService),你應該使用 Subject.associateWith*方法。這些方法確定在最終執行的線程中保 留 Subject,且該 Subject 是可通路的。  

shiro實戰系列(十)之Subject