Shiro————核心设计思想
引言
以此篇博客為引,開啟一個新的專欄分類——Shiro。
之前在工作中有比較快速的學習過Shiro安全框架,但經過一年的荒廢,已經不是很熟悉了,通過這個系列,深入研究和學習Shiro的一些知識,填補安全管理方面的知識漏洞。使我們在web 開發領域更具競爭力,不做只會CRUD的程序員!
一、Shiro介紹
Shiro是一個Java安全框架,執行身份驗證、授權、密碼、會話管理。Shiro是Apache 的一個開源項目,前身是JSecurity 項目,始于2003年初。
Shiro 可以為任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。
shiro 解決了應用安全的四要素:
- 認證 - 用戶身份識別,常被稱為用戶“登錄”;
- 授權 - 訪問控制;
- 密碼加密 - 保護或隱藏數據防止被偷窺;
- 會話管理 - 每用戶相關的時間敏感的狀態。
同時,Shiro另外支持了一些輔助特性:如 Web 應用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素。
二、Shiro的優勢
從 2003 年至今,框架選擇方面的情況已經改變了不少,但今天仍有令人信服的理由讓你選擇 Shiro。其實理由相當多,Apache Shiro:
1、易于使用?- 易用性是這個項目的最終目標。應用安全有可能會非常讓人糊涂,令人沮喪,并被認為是“必要之惡”【譯注:比喻應用安全方面的編程。】。若是能讓它簡化到新手都能很快上手,那它將不再是一種痛苦了。
2、廣泛性?- 沒有其他安全框架可以達到 Apache Shiro 宣稱的廣度,它可以為你的安全需求提供“一站式”服務。
3、靈活性?- Apache Shiro 可以工作在任何應用環境中。雖然它工作在 Web、EJB 和 IoC 環境中,但它并不依賴這些環境。Shiro 既不強加任何規范,也無需過多依賴。
4、Web 能力?- Apache Shiro 對 Web 應用的支持很神奇,允許你基于應用 URL 和 Web 協議(如 REST)創建靈活的安全策略,同時還提供了一套控制頁面輸出的 JSP 標簽庫。
5、可插拔?- Shiro 干凈的 API 和設計模式使它可以方便地與許多的其他框架和應用進行集成。你將看到 Shiro 可以與諸如 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 這類第三方框架無縫集成。
6、支持?- Apache Shiro 是?Apache 軟件基金會成員,這是一個公認為了社區利益最大化而行動的組織。項目開發和用戶組都有隨時愿意提供幫助的友善成員。
三、核心概念
Shiro的核心概念有三個:Subject,SecurityManager 和 Realms。
3.1 Subject
subject 被Shiro 描述為一個主體,對于web應用來說,可以簡單理解為用戶。
這里我們來闡述一個Shiro設計的重要理念,即以主體為展開的安全體系構建。引用一段話:
在考慮應用安全時,你最常問的問題可能是“當前用戶是誰?”或“當前用戶允許做 X 嗎?”。當我們寫代碼或設計用戶界面時,問自己這些問題很平常:應用通常都是基于用戶故事構建的,并且你希望功能描述(和安全)是基于每個用戶的。所以,對于我們而言,考慮應用安全的最自然方式就是基于當前用戶。Shiro 的 API 用它的 Subject 概念從根本上體現了這種思考方式。
在應用程序中,我們可以在任何地方獲取當前操作的用戶主體:
import org.apache.shiro.subject.Subject; import org.apache.shiro.SecurityUtils; ... Subject currentUser = SecurityUtils.getSubject();獲得Subject 后,通過這個對象,我們可以對其進行絕大多數安全操作:登錄、登出、訪問會話、執行授權檢查等。
Shiro 的api非常直觀,它反映了開發者以“每個用戶” 思考安全控制的自然趨勢。
3.2 SecurityManager
Subject 的幕后推手是 SecurityManager,Subject 代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。
SecurityManager?是 Shiro 框架的核心,充當“保護傘”,引用了多個內部嵌套安全組件,它們形成了對象圖。但是,一旦 SecurityManager 及其內部對象圖配置好,它就會退居幕后,應用開發人員幾乎把他們的所有時間都花在 Subject API 調用上。
一個應用只需要一個 SecurityManager,是一個單例對象。它的缺省實現是POJO,Shiro 里的其他組件也是一樣。因此,可以用POJO兼容的任何配置機制進行配置:普通的Java代碼、Spring xml、YAML、和 ini 文件等。基本上,能夠實例化類和調用JavaBean兼容方法的任何配置形式都可以。
比如,通過ini文件進行配置:
[main] cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher cm.hashAlgorithm = SHA-512 cm.hashIterations = 1024 # Base64 encoding (less text): cm.storedCredentialsHexEncoded = falseiniRealm.credentialsMatcher = $cm[main] 段落是配置SecurityManager 對象及其使用的其他任何對象(如 Realm) 的地方,在上面的示例中,我們看到了兩個對象:
1、cm對象,是Shiro 的HashedCredentialsMatcher 類實例,cm 的各屬性通過“嵌套點”語法進行配置。
2、iniRealm對象,被 SecurityManager 用來表示以INI 格式定義的用戶賬戶。
然后,我們在Java代碼中,可以輕而易舉的獲得 SecurityManager對象了:
//1. 裝入 INI 配置 Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");//2. 創建 SecurityManager SecurityManager securityManager = factory.getInstance();//3. 使其可訪問 SecurityUtils.setSecurityManager(securityManager);3.3 Realm
Realm 充當了 Shiro 與應用安全數據間的“橋梁”或者“連接器”。當切實與像用戶帳戶這類安全相關數據進行交互,執行認證(登錄)和授權(訪問控制)時,Shiro 會從應用配置的 Realm 中查找很多內容。
從某種意義上講,Realm 實際上就是一個安全相關的 DAO:它封裝了數據源的連接細節,并在需要時將相關數據提供給 Shiro。
注意:在配置Shiro 時,必須指定至少一個 Realm ,可以配置多個。
Shiro 內置了一些Realm ,支持多種數據源的連接,如JDBC、LDAP、INI文件的連接等。另外,可以自定義Realm 實現,方便個性化的應用場景。
四、典型的安全場景概述
應用安全的四要素:認證、授權、會話管理、加密。
4.1 認證
雖然有些武斷,但是一般web 應用認證就是登錄功能。也就是說,當用戶使用應用進行認證時,他們就在證明他們就是自己所說的那個人。
這是一個典型的三步過程:
1、收集用戶身份信息,成為當事人(principal),以及身份的支持證明,稱為證書(Credential)。
2、將當事人和證書提交給系統。
3、如果提交的證書與系統期望的該用戶身份(當事人)匹配,該用戶就被認為是經過認證的,反之則被認為未經認證的。
Shiro 以簡單直觀的方式支持同樣的流程。Shiro 有一套以Subject 為中心的API,幾乎你想要用 Shiro 在運行時完成的所有事情都能通過與當前執行的 Subject 進行交互而達成。因此,要登錄 Subject,只需要簡單地調用它的 login 方法。傳入表示被提交當事人和證書(在這種情況下,就是用戶名和密碼)的 AuthenticationToken 實例。
//1. 接受提交的當事人和證書: AuthenticationToken token = new UsernamePasswordToken(username, password);//2. 獲取當前 Subject: Subject currentUser = SecurityUtils.getSubject(); //3. 登錄: currentUser.login(token);可以看到,Shiro的操作及其簡潔和自然,這也是Shiro 慣有的風格。在調用 login()方法后,SecurityManager 會收到AuthenticationToken,并將其發送給已配置的 Realm,執行必須的認證檢查,以往我們手動去數據庫中進行校驗和匹配的時代已經過去了,這些所有的操作,全部由Shiro 幫我們自動完成。當數據經過Realm 的檢查后發現無法匹配,那么Shiro 就會返回 AuthenticationException 異常的子類,通過這些子類,我們可以精確的控制想要返回給用戶的錯誤信息:
try {currentUser.login(token); } catch (IncorrectCredentialsException ice) {… } catch (LockedAccountException lae) {… } … catch (AuthenticationException ae) {… }如果沒有拋出任何異常,則證明 Subject 登錄成功,就被認為是已認證的。
4.2 授權
授權實質上就是訪問控制,控制已認證的用戶能夠訪問應用的哪些內容,如資源、頁面等。
多數用戶執行訪問控制是通過 角色 + 權限 的概念來完成的。角色是所有用戶個體的一個分組,如管理員、普通用戶、商家等;而權限 則表示具體能夠操作的行為,比如查詢所有用戶、刪除某些用戶、修改信息等等,是與具體應用資源直接掛鉤的。
用戶、角色和 權限三者往往通過 角色 來進行轉換,用戶和權限之間通常不進行直接綁定:
我們可以通過shiro的校驗方法,來便捷地實現分支語句:
if ( subject.hasRole("administrator") ) {// 顯示‘Create User’按鈕 } else {// 按鈕置灰? }雖然,在概念上,權限與角色直接掛鉤,但其最終效果還是要落實到具體的某個用戶是否具有某個權限,為此,Shiro也為我們提供了相應的校驗方法:
if ( subject.isPermitted("user:create") ) {// 顯示‘Create User’按鈕 } else {// 按鈕置灰? }這樣,任何具有“user:create”權限的角色或用戶都可以點擊‘Create User’按鈕,并且這些角色和指派甚至可以在運行時改變,這給你提供了一個非常靈活的安全模型。
上例中,"user:create" 字符串是一種遵循特定規則的權限描述符,這在后面的文章中會單獨介紹。具體詳情可了解:http://shiro.apache.org/permissions.html
上面這些權限的調用,最終都會發送到SecurityManager中,它會咨詢 Realm 做出自己的訪問控制決定。必要時,還允許單個 Realm 同時響應認證和授權操作。
4.3 會話管理
在以往的Servlet應用中,我們最常使用的會話對象就是 HttpSession 對象。
在Shiro 中,也有屬于自己的會話管理機制和用戶的會話對象。Shiro 允許開發者在任何應用或架構層一致地使用 Session API。
它為任何應用(從小型后臺獨立應用到大型集群 Web 應用)提供了一個會話編程范式。這意味著,那些希望使用會話的應用開發者,不必被迫使用 Servlet 或 EJB 容器了。或者,如果正在使用這些容器,開發者現在也可以選擇使用在任何層統一一致的會話 API,取代 Servlet 或 EJB 機制。
Shiro 會話最重要的一個好處或許就是它們是獨立于容器的。這個特性的作用非常巨大,設想一下會話集群。對集群會話來講,支持容錯和故障轉移有多少種容器特定的方式?Tomcat 的方式與 Jetty 的不同,而 Jetty 又和 Websphere 不一樣,等等。但通過 Shiro 會話,你可以獲得一個容器無關的集群解決方案。
Shiro 的架構允許可插拔的會話數據存儲,如企業緩存、關系數據庫、NoSQL 系統等。這意味著,只要配置會話集群一次,它就會以相同的方式工作,跟部署環境無關 - Tomcat、Jetty、JEE 服務器或者獨立應用。不管如何部署應用,毋須重新配置應用。
獲取當前用戶的Session 對象,我們可以使用下面這樣的方法:
Session session = subject.getSession(); Session session = subject.getSession(boolean create);上面這些方法在概念上等同于HttpServletRequest API。第一個方法會返回 Subject 的現有會話,或者如果還沒有會話,它會創建一個新的并將之返回。第二個方法接受一個布爾參數,這個參數用于判定會話不存在時是否創建新會話。一旦獲得 Shiro 的會話,你幾乎可以像使用 HttpSession 一樣使用它。Shiro 保留的 HttpSession 的使用體驗,但不同的是 Shiro 可以在任何應用中使用會話機制,不僅限于Web應用。
Shiro? Session 的一些方法:
Session session = subject.getSession(); session.getAttribute("key", someValue); Date start = session.getStartTimestamp(); Date timestamp = session.getLastAccessTime(); session.setTimeout(millis); ...4.4 加密
在加密方面,Shiro 盡可能的簡化加密處理的步驟,并讓JDK的加密支持可用。
注意一點,加密并不是特定于Subject 的,加密的特性是 Shiro 的一部分,但不特定于僅僅對 Subject 的處理。我們可以在任何地方使用 Shiro 的加密支持,甚至在不使用 Subject 的情況下。
對于加密支持,Shiro 真正關心的兩個領域是加密哈希(又名消息摘要)和加密密碼。
傳統的JDK加密操作過于復雜,而且是基于笨拙的工廠靜態方法api,它并不是面向對象的設計:
try {MessageDigest md = MessageDigest.getInstance("MD5");md.digest(bytes);byte[] hashed = md.digest(); } catch (NoSuchAlgorithmException e) {e.printStackTrace(); }如果在某些場景下頻繁會用到哈希加密的情況,那么開發者就必須要封裝之后才能比較輕松的使用JDK 的加密支持,否則將會是一個不小的工作量。Shiro 為我們提供了這樣的便捷,對于類似的功能,只需要簡單的幾行代碼就可以完成,同時也省去了一些老舊的和不必要的異常捕獲:
// 面向對象的MD5加密方式 String hex = new Md5Hash(myFile).toHex(); // SHA-512 String encodedPassword = new Sha512Hash(password, salt, count).toBase64();剛剛提到的這些是一些必要的哈希算法API,另一個Shiro 關心的事情就是 加密密碼。
我們知道,加密是使用密鑰對數據進行可逆轉換的加密算法。我們使用其保證數據的安全,尤其是傳輸或存儲數據時,以及在數據容易被窺探的時候。
同樣 JDK 的加密API也非常復雜和難用,而Shiro 通過引入它的 CipherService API 試圖簡化加密密碼的整個概念。
CipherService 是多數開發者在保護數據時夢寐以求的東西:簡單、無狀態、線程安全的 API,能夠在一次方法調用中對整個數據進行加密或解密。你所需要做的只是提供你的密鑰,就可根據需要加密或解密。
AesCipherService cipherService = new AesCipherService(); cipherService.setKeySize(256);// 創建一個測試密鑰: byte[] testKey = cipherService.generateNewKey(); // 加密文件的字節: byte[] encrypted = cipherService.encrypt(fileBytes, testKey);較之 JDK 的 Cipher API,Shiro 的示例要簡單的多:
- 你可以直接實例化一個 CipherService - 沒有奇怪或讓人混亂的工廠方法;
- Cipher 配置選項可以表示成 JavaBean - 兼容的 getter 和 setter 方法 - 沒有了奇怪和難以理解的“轉換字符串”;
- 加密和解密在單個方法調用中完成;
- 沒有強加的 Checked Exception。如果愿意,可以捕獲 Shiro 的 CryptoException。
Shiro 的 CipherService API 還有其他好處,如同時支持基于字節數組的加密 / 解密(稱為“塊”操作)和基于流的加密 / 解密(如加密音頻或視頻)。
五、Web支持
Shiro 附帶了一個幫助保護web應用的強健的web 支持模塊,就目前大火的spring boot框架,除了配置基本的依賴和Shiro 的核心單例組件 - SecurityManager 之外,可能剩下的就只是要配置一個Shiro Servlet 過濾器了。
關于Shiro 的過濾器,后面的文章還會詳細介紹,總之,當我們完成了 這些前期的必要的配置工作后,Shiro Filter 就會過濾每個請求,并確保在請求期間特定的 Subject 是可訪問的。同時由于它過濾了每個請求,你可以執行安全特定的邏輯以保證只有滿足一定標準的請求才被允許通過。
5.1 URL 過濾器
Shiro 通過其創新的 URL 過濾器鏈功能支持安全特定的過濾規則。
[urls] /assets/** = anon /user/signup = anon /user/** = user /rpc/rest/** = perms[rpc:invoke], authc /** = authc請不要在意這些配置形式,感受它所代表的含義和強大的特性內核。
對于每一行,等號左邊的值表示相對上下文的 Web 應用路徑。等號右邊的值定義了過濾器鏈 - 一個逗號分隔的有序 Servlet 過濾器列表,它會針對給出的路徑進行執行。每個過濾器都是普通的 Servlet 過濾器,你看到的上面的過濾器名字(anon,user,perms,authc)是 Shiro 內置的安全相關的特殊過濾器。你可以搭配這些安全過濾器來創建高度定制的安全體驗。你還可以指定任何其他現有的 Servlet 過濾器。
5.2 Web 會話管理
對于 Web 應用,Shiro 缺省將使用我們習以為常的 Servlet 容器會話作為其會話基礎設施。即,當你調用 subject.getSession() 和 subject.getSession(boolean) 方法時,Shiro 會返回 Servlet 容器的 HttpSession 實例支持的 Session 實例。
這種方式的曼妙之處在于調用 subject.getSession() 的業務層代碼會跟一個 Shiro Session 實例交互 - 還沒有“認識”到它正跟一個基于 Web 的 HttpSession 打交道。這在維護架構層之間的清晰隔離時,是一件非常好的事情。
另外,當需要使用與容器無關的會話特性時,我們也可以開啟Shiro 的原生會話管理。不同于傳統的HttpServletRequest.getSession()和 HttpSession API只能和Servlet 容器打交道,Shiro的原生會話管理依然可以讓用戶使用相同的 HTTPServletRequest 和 HttpSession 調用完成與原生會話的協作,而不需要重構這些代碼。這是因為 Shiro 完整實現了 Servlet規范中的 Session 部分以在 Web 應用中支持原生會話,因此開發者的所有 HTTPSession 的調用都會被委托給 Shiro 內部的原生會話 API。
六、附加特性
Apache Shiro 框架還包含有對保護 Java 應用非常有用的其他特性,如:
- 為維持跨線程的 Suject 提供了線程和并發支持(支持 Executor 和 ExecutorService);
- 為了將執行邏輯作為一種特殊的 Subject,支持 Callable 和 Runnable 接口;
- 為了表現為另一個 Subject 的身份,支持“Run As”(比如,在管理應用中有用);
- 支持測試工具,這樣可以很容易的對 Shiro 的安全代碼進行單元測試和集成測試。
七、框架的局限
如下是 Shiro 還未解決,但是值得知道的:
- 虛擬機級別的問題:Apache Shiro 當前還未處理虛擬機級別的安全,比如基于訪問控制策略,阻止類加載器中裝入某個類。然而,Shiro 集成現有的 JVM 安全操作并非白日做夢 - 只是沒人給項目貢獻這方面的工作。
- 多階段認證:目前,Shiro 不支持“多階段”認證,即用戶可能通過一種機制登錄,當被要求再次登錄時,使用另一種機制登錄。這在基于 Shiro 的應用中已經實現,但是通過應用預先收集所有必需信息再跟 Shiro 交互。這個功能在 Shiro 的未來版本中非常有可能得到支持。
- Realm 寫操作:目前所有 Realm 實現都支持“讀”操作來獲取驗證和授權數據以執行登錄和訪問控制。諸如創建用戶帳戶、組和角色或與用戶相關的角色組和權限這類“寫”操作還不支持。這是因為支持這些操作的應用數據模型變化太大,很難為所有的 Shiro 用戶強制定義“寫”API。
八、總結
Apache Shiro 是一個功能齊全、健壯、通用的 Java 安全框架,你可以用其為你的應用護航。通過簡化應用安全的四個領域,即認證、授權、會話管理和加密,在真實應用中,應用安全能更容易被理解和實現。Shiro 的簡單架構和兼容 JavaBean 使其幾乎能夠在任何環境下配置和使用。附加的 Web 支持和輔助功能,比如多線程和測試支持,讓這個框架為應用安全提供了“一站式”服務。
鳴謝:
《讓 Apache Shiro 保護你的應用》
總結
以上是生活随笔為你收集整理的Shiro————核心设计思想的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 复制含有随机指针节点的链表~哈希表的使用
- 下一篇: LeetCode算法入门- 4Sum -