在嘗試保護你的應用時,你是否有過挫敗感?是否覺得現有的Java安全解決方案難以使用,只會讓你更糊涂?本文介紹的Apache Shiro,是一個不同尋常的Java安全框架,為保護應用提供了簡單而強大的方法。本文還解釋了Apache Shiro的項目目標、架構理念以及如何使用Shiro為應用安全保駕護航。
什么是Apache Shiro?
Apache Shiro(發音為“shee-roh”,日語“堡壘(Castle)”的意思)是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理功能,可為任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。
Shiro為解決下列問題(我喜歡稱它們為應用安全的四要素)提供了保護應用的API:
- 認證 - 用戶身份識別,常被稱為用戶“登錄”;
- 授權 - 訪問控制;
- 密碼加密 - 保護或隱藏數據防止被偷窺;
- 會話管理 - 每用戶相關的時間敏感的狀態。
Shiro還支持一些輔助特性,如Web應用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素。
為何要創建Apache Shiro?
對于一個框架來講,使其有存在價值的最好例證就是有讓你去用它的原因,它應該能完成一些別人無法做到的事情。要理解這一點,需要了解Shiro的歷史以及創建它時的其他替代方法。
在2008年加入Apache軟件基金會之前,Shiro已經5歲了,之前它被稱為JSecurity項目,始于2003年初。當時,對于Java應用開發人員而言,沒有太多的通用安全替代方案 - 我們被Java認證/授權服務(或稱為JAAS)緊緊套牢了。JAAS有太多的缺點 - 盡管它的認證功能尚可忍受,但授權方面卻顯得拙劣,用起來令人沮喪。此外,JAAS跟虛擬機層面的安全問題關系非常緊密,如判斷JVM中是否允許裝入一個類。作為應用開發者,我更關心應用最終用戶能做什么,而不是我的代碼在JVM中能做什么。
由于當時正從事應用開發,我也需要一個干凈、容器無關的會話機制。在當時,“這場游戲”中唯一可用的會話是HttpSessions,它需要Web容器;或是EJB 2.1里的有狀態會話Bean,這又要EJB容器。而我想要的一個與容器脫鉤、可用于任何我選擇的環境中的會話。
最后就是加密問題。有時,我們需要保證數據安全,但是Java密碼架構(Java Cryptography Architecture)讓人難以理解,除非你是密碼學專家。API里到處都是Checked Exception,用起來很麻煩。我需要一個干凈、開箱即用的解決方案,可以在需要時方便地對數據加密/解密。
于是,縱觀2003年初的安全狀況,你會很快意識到還沒有一個大一統的框架滿足所有上述需求。有鑒于此,JSecurity(即之后的Apache Shiro)誕生了。
今天,你為何愿意使用Apache Shiro?
從2003年至今,框架選擇方面的情況已經改變了不少,但今天仍有令人信服的理由讓你選擇Shiro。其實理由相當多,Apache Shiro:
-
易于使用
?- 易用性是這個項目的最終目標。應用安全有可能會非常讓人糊涂,令人沮喪,并被認為是“必要之惡”【譯注:比喻應用安全方面的編程。】。若是能讓它簡化到新手都能很快上手,那它將不再是一種痛苦了。
-
廣泛性
?- 沒有其他安全框架可以達到Apache Shiro宣稱的廣度,它可以為你的安全需求提供“一站式”服務。
-
靈活性
?- Apache Shiro可以工作在任何應用環境中。雖然它工作在Web、EJB和IoC環境中,但它并不依賴這些環境。Shiro既不強加任何規范,也無需過多依賴。
-
Web能力
?- Apache Shiro對Web應用的支持很神奇,允許你基于應用URL和Web協議(如REST)創建靈活的安全策略,同時還提供了一套控制頁面輸出的JSP標簽庫。
-
可插拔
?- Shiro干凈的API和設計模式使它可以方便地與許多的其他框架和應用進行集成。你將看到Shiro可以與諸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin這類第三方框架無縫集成。
-
支持
?- Apache Shiro是Apache軟件基金會成員,這是一個公認為了社區利益最大化而行動的組織。項目開發和用戶組都有隨時愿意提供幫助的友善成員。像Katasoft這類商業公司,還可以給你提供需要的專業支持和服務。
誰在用Shiro?
Shiro及其前身JSecurity已被各種規模和不同行業的公司項目采用多年。自從成為Apache軟件基金會的頂級項目后,站點流量和使用呈持續增長態勢。許多開源社區也正在用Shiro,這里有些例子如Spring,Grails,Wicket,Tapestry,Tynamo,Mule和Vaadin。
如Katasoft,Sonatype,MuleSoft這樣的商業公司,一家大型社交網絡和多家紐約商業銀行都在使用Shiro來保護他們的商業軟件和站點。
核心概念:Subject,SecurityManager和Realms
既然已經描述了Shiro的好處,那就讓我們看看它的API,好讓你能夠有個感性認識。Shiro架構有三個主要概念 - Subject,SecurityManager和Realms。
Subject
在考慮應用安全時,你最常問的問題可能是“當前用戶是誰?”或“當前用戶允許做X嗎?”。當我們寫代碼或設計用戶界面時,問自己這些問題很平常:應用通常都是基于用戶故事構建的,并且你希望功能描述(和安全)是基于每個用戶的。所以,對于我們而言,考慮應用安全的最自然方式就是基于當前用戶。Shiro的API用它的Subject概念從根本上體現了這種思考方式。
Subject一詞是一個安全術語,其基本意思是“當前的操作用戶”。稱之為“用戶”并不準確,因為“用戶”一詞通常跟人相關。在安全領域,術語“Subject”可以是人,也可以是第三方進程、后臺帳戶(Daemon Account)或其他類似事物。它僅僅意味著“當前跟軟件交互的東西”。但考慮到大多數目的和用途,你可以把它認為是Shiro的“用戶”概念。在代碼的任何地方,你都能輕易的獲得Shiro Subject,參見如下代碼:
清單1. 獲得Subject
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();
一旦獲得Subject,你就可以立即獲得你希望用Shiro為當前用戶做的90%的事情,如登錄、登出、訪問會話、執行授權檢查等 - 稍后還會看到更多。這里的關鍵點是Shiro的API非常直觀,因為它反映了開發者以‘每個用戶’思考安全控制的自然趨勢。同時,在代碼的任何地方都能很輕松地訪問Subject,允許在任何需要的地方進行安全操作。
SecurityManager
Subject的“幕后”推手是SecurityManager。Subject代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。它是Shiro框架的核心,充當“保護傘”,引用了多個內部嵌套安全組件,它們形成了對象圖。但是,一旦SecurityManager及其內部對象圖配置好,它就會退居幕后,應用開發人員幾乎把他們的所有時間都花在Subject API調用上。
那么,如何設置SecurityManager呢?嗯,這要看應用的環境。例如,Web應用通常會在Web.xml中指定一個Shiro Servlet Filter,這會創建SecurityManager實例,如果你運行的是一個獨立應用,你需要用其他配置方式,但有很多配置選項。
一個應用幾乎總是只有一個SecurityManager實例。它實際是應用的Singleton(盡管不必是一個靜態Singleton)。跟Shiro里的幾乎所有組件一樣,SecurityManager的缺省實現是POJO,而且可用POJO兼容的任何配置機制進行配置 - 普通的Java代碼、Spring XML、YAML、.properties和.ini文件等。基本來講,能夠實例化類和調用JavaBean兼容方法的任何配置形式都可使用。
為此,Shiro借助基于文本的INI配置提供了一個缺省的“公共”解決方案。INI易于閱讀、使用簡單并且需要極少依賴。你還能看到,只要簡單地理解對象導航,INI可被有效地用于配置像SecurityManager那樣簡單的對象圖。注意,Shiro還支持Spring XML配置及其他方式,但這里只我們只討論INI。
下列清單2列出了基于INI的Shiro最簡配置:
清單2. 用INI配置Shiro
[main]
cm = org.apache.shiro.authc.credential.HashedCredentialsMatcher
cm.hashAlgorithm = SHA-512
cm.hashIterations = 1024
# Base64 encoding (less text):
cm.storedCredentialsHexEncoded = false
iniRealm.credentialsMatcher = $cm
[users]
jdoe = TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJpcyByZWFzb2
asmith = IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbXNoZWQsIG5vdCB
在清單2中,我們看到了用于配置SecurityManager實例的INI配置例子。有兩個INI段落:[main]和[users].
[main]段落是配置SecurityManager對象及其使用的其他任何對象(如Realms)的地方。在示例中,我們看到配置了兩個對象:
- cm對象,是Shiro的HashedCredentialsMatcher類實例。如你所見,cm實例的各屬性是通過“嵌套點”語法進行配置的 - 在清單3中可以看到IniSecurityManagerFactory使用的慣例,這種方法代表了對象圖導航和屬性設置。
- iniRealm對象,它被SecurityManager用來表示以INI格式定義的用戶帳戶。
[users]段落是指定用戶帳戶靜態列表的地方 - 為簡單應用或測試提供了方便。
就介紹而言,詳細了解每個段落的細節并不是重點。相反,看到INI配置是一種配置Shiro的簡單方式才是關鍵。關于INI配置的更多細節,請參見Shiro文檔。
清單3. 裝入shiro.ini配置文件
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.util.Factory;
...
//1.裝入INI配置
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2. 創建SecurityManager
SecurityManager securityManager = factory.getInstance();
//3. 使其可訪問
SecurityUtils.setSecurityManager(securityManager);
在清單3的示例中,我們看到有三步:
- 裝入用來配置SecurityManager及其構成組件的INI配置文件;
- 根據配置創建SecurityManager實例(使用Shiro的工廠概念,它表述了工廠方法設計模式);
- 使應用可訪問SecurityManager Singleton。在這個簡單示例中,我們將它設置為VM靜態Singleton,但這通常不是必須的 - 你的應用配置機制可以決定你是否需要使用靜態存儲。
Realms
Shiro的第三個也是最后一個概念是Realm。Realm充當了Shiro與應用安全數據間的“橋梁”或者“連接器”。也就是說,當切實與像用戶帳戶這類安全相關數據進行交互,執行認證(登錄)和授權(訪問控制)時,Shiro會從應用配置的Realm中查找很多內容。
從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了數據源的連接細節,并在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用于認證和(或)授權。配置多個Realm是可以的,但是至少需要一個。
Shiro內置了可以連接大量安全數據源(又名目錄)的Realm,如LDAP、關系數據庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數據源的自己的Realm實現。下面的清單4是通過INI配置Shiro使用LDAP目錄作為應用Realm的示例。
清單4. Realm配置示例片段:連接存儲用戶數據的LDAP
[main]
ldapRealm = org.apache.shiro.realm.ldap.JndiLdapRealm
ldapRealm.userDnTemplate = uid={0},ou=users,dc=mycompany,dc=com
ldapRealm.contextFactory.url = ldap://ldapHost:389
ldapRealm.contextFactory.authenticationMechanism = DIGEST-MD5
既然已經了解如何建立一個基本的Shiro環境,下面讓我們來討論,作為一名開發者該如何使用這個框架。
認證
認證是核實用戶身份的過程。也就是說,當用戶使用應用進行認證時,他們就在證明他們就是自己所說的那個人。有時這也理解為“登錄”。它是一個典型的三步驟過程。
- 收集用戶的身份信息,稱為當事人(principal),以及身份的支持證明,稱為證書(Credential)。
- 將當事人和證書提交給系統。
- 如果提交的證書與系統期望的該用戶身份(當事人)匹配,該用戶就被認為是經過認證的,反之則被認為未經認證的。
這個過程的常見例子是大家都熟悉的“用戶/密碼”組合。多數用戶在登錄軟件系統時,通常提供自己的用戶名(當事人)和支持他們的密碼(證書)。如果存儲在系統中的密碼(或密碼表示)與用戶提供的匹配,他們就被認為通過認證。
Shiro以簡單直觀的方式支持同樣的流程。正如我們前面所說,Shiro有一個以Subject為中心的API - 幾乎你想要用Shiro在運行時完成的所有事情都能通過與當前執行的Subject進行交互而達成。因此,要登錄Subject,只需要簡單地調用它的login方法,傳入表示被提交當事人和證書(在這種情況下,就是用戶名和密碼)的AuthenticationToken實例。示例如清單5中所示:
清單5. Subject登錄
//1. 接受提交的當事人和證書:
AuthenticationToken token =
new UsernamePasswordToken(username, password);
//2. 獲取當前Subject:
Subject currentUser = SecurityUtils.getSubject();
//3. 登錄:
currentUser.login(token);
你可以看到,Shiro的API很容易地就反映了這個常見流程。你將會在所有的Subject操作中繼續看到這種簡單風格。在調用了login方法后,SecurityManager會收到AuthenticationToken,并將其發送給已配置的Realm,執行必須的認證檢查。每個Realm都能在必要時對提交的AuthenticationTokens作出反應。但是如果登錄失敗了會發生什么?如果用戶提供了錯誤密碼又會發生什么?通過對Shiro的運行時AuthenticationException做出反應,你可以控制失敗,參見清單6。
清單6. 控制失敗的登錄
//3. 登錄:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
…
} catch (LockedAccountException lae) {
…
}
…
catch (AuthenticationException ae) {…
}
你可以選擇捕獲AuthenticationException的一個子類,作出特定的響應,或者對任何AuthenticationException做一般性處理(例如,顯示給用戶普通的“錯誤的用戶名或密碼”這類消息)。選擇權在你,可以根據應用需要做出選擇。
Subject登錄成功后,他們就被認為是已認證的,通常你會允許他們使用你的應用。但是僅僅證明了一個用戶的身份并不意味著他們可以對你的應用為所欲為。這就引出了另一個問題,“我如何控制用戶能做或不能做哪些事情?”,決定用戶允許做哪些事情的過程被稱為授權。下面我們將談談Shiro如何進行授權。
授權
授權實質上就是訪問控制 - 控制用戶能夠訪問應用中的哪些內容,比如資源、Web頁面等等。多數用戶執行訪問控制是通過使用諸如角色和權限這類概念完成的。也就是說,通常用戶允許或不允許做的事情是根據分配給他們的角色或權限決定的。那么,通過檢查這些角色和權限,你的應用程序就可以控制哪些功能是可以暴露的。如你期望的,Subject API讓你可以很容易的執行角色和權限檢查。如清單7中的代碼片段所示:如何檢查Subject被分配了某個角色:
列表7. 角色檢查
if ( subject.hasRole(“administrator”) ) {
//顯示‘Create User’按鈕
} else {
//按鈕置灰?
}
如你所見,你的應用程序可基于訪問控制檢查打開或關閉某些功能。
權限檢查是執行授權的另一種方法。上例中的角色檢查有個很大的缺陷:你無法在運行時增刪角色。角色名字在這里是硬編碼,所以,如果你修改了角色名字或配置,你的代碼就會亂套!如果你需要在運行時改變角色含義,或想要增刪角色,你必須另辟蹊徑。
為此,Shiro支持了權限(permissions)概念。權限是功能的原始表述,如‘開門’,‘創建一個博文’,‘刪除‘jsmith’用戶’等。通過讓權限反映應用的原始功能,在改變應用功能時,你只需要改變權限檢查。進而,你可以在運行時按需將權限分配給角色或用戶。
如清單8中,我們重寫了之前的用戶檢查,取而代之使用權限檢查。
清單8. 權限檢查
if ( subject.isPermitted(“user:create”) ) {
//顯示‘Create User’按鈕
} else {
//按鈕置灰?
}
這樣,任何具有“user:create”權限的角色或用戶都可以點擊‘Create User’按鈕,并且這些角色和指派甚至可以在運行時改變,這給你提供了一個非常靈活的安全模型。
“user:create”字符串是一個權限字符串的例子,它遵循特定的解析慣例。Shiro借助它的WildcardPermission支持這種開箱即用的慣例。盡管這超出了本文的范圍,你會看到在創建安全策略時,WildcardPermission非常靈活,甚至支持像實例級別訪問控制這樣的功能。
清單9. 實例級別的權限檢查
if ( subject.isPermitted(“user:delete:jsmith”) ) {
//刪除‘jsmith’用戶
} else {
//不刪除‘jsmith’
}
該例表明,你可以對你需要的單個資源進行訪問控制,甚至深入到非常細粒度的實例級別。如果愿意,你甚至還可以發明自己的權限語法。參見Shiro Permission文檔可以了解更多內容。最后,就像使用認證那樣,上述調用最終會轉向SecurityManager,它會咨詢Realm做出自己的訪問控制決定。必要時,還允許單個Realm同時響應認證和授權操作。
以上就是對Shiro授權功能的簡要概述。雖然多數安全框架止于授權和認證,但Shiro提供了更多功能。下面,我們將談談Shiro的高級會話管理功能。
會話管理
在安全框架領域,Apache Shiro提供了一些獨特的東西:可在任何應用或架構層一致地使用Session API。即,Shiro為任何應用提供了一個會話編程范式 - 從小型后臺獨立應用到大型集群Web應用。這意味著,那些希望使用會話的應用開發者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用這些容器,開發者現在也可以選擇使用在任何層統一一致的會話API,取代Servlet或EJB機制。
但Shiro會話最重要的一個好處或許就是它們是獨立于容器的。這具有微妙但非常強大的影響。例如,讓我們考慮一下會話集群。對集群會話來講,支持容錯和故障轉移有多少種容器特定的方式?Tomcat的方式與Jetty的不同,而Jetty又和Websphere不一樣,等等。但通過Shiro會話,你可以獲得一個容器無關的集群解決方案。Shiro的架構允許可插拔的會話數據存儲,如企業緩存、關系數據庫、NoSQL系統等。這意味著,只要配置會話集群一次,它就會以相同的方式工作,跟部署環境無關 - Tomcat、Jetty、JEE服務器或者獨立應用。不管如何部署應用,毋須重新配置應用。
Shiro會話的另一好處就是,如果需要,會話數據可以跨客戶端技術進行共享。例如,Swing桌面客戶端在需要時可以參與相同的Web應用會話中 - 如果最終用戶同時使用這兩種應用,這樣的功能會很有用。那你如何在任何環境中訪問Subject的會話呢?請看下面的示例,里面使用了Subject的兩個方法。
清單10. Subject的會話
Session session = subject.getSession();
Session session = subject.getSession(boolean create);
如你所見,這些方法在概念上等同于HttpServletRequest API。第一個方法會返回Subject的現有會話,或者如果還沒有會話,它會創建一個新的并將之返回。第二個方法接受一個布爾參數,這個參數用于判定會話不存在時是否創建新會話。一旦獲得Shiro的會話,你幾乎可以像使用HttpSession一樣使用它。Shiro團隊覺得對于Java開發者,HttpSession API用起來太舒服了,所以我們保留了它的很多感覺。當然,最大的不同在于,你可以在任何應用中使用Shiro會話,不僅限于Web應用。清單11中顯示了這種相似性。
清單11. 會話的方法
Session session = subject.getSession();
session.getAttribute("key", someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis); ...
加密
加密是隱藏或混淆數據以避免被偷窺的過程。在加密方面,Shiro的目標是簡化并讓JDK的加密支持可用。
清楚一點很重要,一般情況下,加密不是特定于Subject的,所以它是Shiro API的一部分,但并不特定于Subject。你可以在任何地方使用Shiro的加密支持,甚至在不使用Subject的情況下。對于加密支持,Shiro真正關注的兩個領域是加密哈希(又名消息摘要)和加密密碼。下面我們來看看這兩個方面的詳細描述。
哈希
如果你曾使用過JDK的MessageDigest類,你會立刻意識到它的使用有點麻煩。MessageDigest類有一個笨拙的基于工廠的靜態方法API,它不是面向對象的,并且你被迫去捕獲那些永遠都不必捕獲的Checked Exceptions。如果需要輸出十六進制編碼或Base64編碼的消息摘要,你只有靠自己 - 對上述兩種編碼,沒有標準的JDK支持它們。Shiro用一種干凈而直觀的哈希API解決了上述問題。
打個比方,考慮比較常見的情況,使用MD5哈希一個文件,并確定該哈希的十六進制值。被稱為‘校驗和’,這在提供文件下載時常用到 - 用戶可以對下載文件執行自己的MD5哈希。如果它們匹配,用戶完全可以認定文件在傳輸過程中沒有被篡改。
不使用Shiro,你需要如下步驟才能完成上述內容:
- 將文件轉換成字節數組。JDK中沒有干這事的,故而你需要創建一個輔助方法用于打開FileInputStream,使用字節緩存區,并拋出相關的IOExceptions,等等。
- 使用MessageDigest類對字節數組進行哈希,處理相關異常,如清單12所示。
- 將哈希后的字節數組編碼成十六進制字符。JDK中還是沒有干這事的,你依舊需要創建另外一個輔助方法,有可能在你的實現中會使用位操作和位移動。
清單12. JDK的消息摘要
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.digest(bytes);
byte[] hashed = md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
對于這樣簡單普遍的需求,這個工作量實在太大了。現在看看Shiro是如何做同樣事情的:
String hex = new Md5Hash(myFile).toHex();
當使用Shiro簡化所有這些工作時,一切都非常簡單明了。完成SHA-512哈希和密碼的Base64編碼也一樣簡單。
String encodedPassword = new Sha512Hash(password, salt, count).toBase64();
你可以看到Shiro對哈希和編碼簡化了不少,挽救了你處理在這類問題上所消耗的腦細胞。
密碼
加密是使用密鑰對數據進行可逆轉換的加密算法。我們使用其保證數據的安全,尤其是傳輸或存儲數據時,以及在數據容易被窺探的時候。
如果你曾經用過JDK的Cryptography API,特別是javax.crypto.Cipher類,你會知道它是一頭需要馴服的極其復雜的野獸。對于初學者,每個可能的加密配置總是由一個javax.crypto.Cipher實例表示。必須進行公鑰/私鑰加密?你得用Cipher。需要為流操作使用塊加密器(Block Cipher)?你得用Cipher。需要創建一個AES 256位Cipher來保護數據?你得用Cipher。你懂的。
那么如何創建你需要的Cipher實例?您得創建一個非直觀、標記分隔的加密選項字符串,它被稱為“轉換字符串(transformation string)”,把該字符串傳給Cipher.getInstance靜態工廠方法。這種字符串方式的cipher選項,并沒有類型安全以確保你正在用有效的選項。這也暗示沒有JavaDoc幫你了解相關選項。并且,如果字符串格式組織不正確,你還需要進一步處理Checked Exception,即便你知道配置是正確的。如你所見,使用JDK Cipher是一項相當繁重的任務。很久以前,這些技術曾經是Java API的標準,但是世事變遷,我們需要一種更簡單的方法。
Shiro通過引入它的CipherService API試圖簡化加密密碼的整個概念。CipherService是多數開發者在保護數據時夢寐以求的東西:簡單、無狀態、線程安全的API,能夠在一次方法調用中對整個數據進行加密或解密。你所需要做的只是提供你的密鑰,就可根據需要加密或解密。如下列清單13中,使用256位AES加密:
清單13. Apache Shiro的加密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還有其他好處,如同時支持基于字節數組的加密/解密(稱為“塊”操作)和基于流的加密/解密(如加密音頻或視頻)。
不必再忍受Java Cryptography帶來的痛苦。Shiro的Cryptography支持就是為了減少你在確保數據安全上付出的努力。
Web支持
最后,但并非不重要,我們將簡單介紹一下Shiro的Web支持。Shiro附帶了一個幫助保護Web應用的強建的Web支持模塊。對于Web應用,安裝Shiro很簡單。唯一需要做的就是在web.xml中定義一個Shiro Servlet過濾器。清單14中,列出了代碼。
清單14. web.xml中的ShiroFilter
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>
org.apache.shiro.web.servlet.IniShiroFilter
</filter-class>
<!-- 沒有init-param屬性就表示從classpath:shiro.ini裝入INI配置 -->
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
這個過濾器可以讀取上述shiro.ini配置,這樣不論什么開發環境,你都擁有了一致的配置體驗。一旦完成配置,Shiro Filter就會過濾每個請求并且確保在請求期間特定請求的Subject是可訪問的。同時由于它過濾了每個請求,你可以執行安全特定的邏輯以保證只有滿足一定標準的請求才被允許通過。
URL特定的Filter鏈
Shiro通過其創新的URL過濾器鏈功能支持安全特定的過濾規則。它允許你為任何匹配的URL模式指定非正式的過濾器鏈。這意味著, 使用Shiro的過濾器機制,你可以很靈活的強制安全規則(或者規則的組合) - 其程度遠遠超過你單獨在web.xml中定義過濾器時所獲得的。清單15中顯示了Shiro INI中的配置片段。
清單15. 路徑特定的Filter鏈
[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc
如你所見,Web應用可以使用[urls] INI段落。對于每一行,等號左邊的值表示相對上下文的Web應用路徑。等號右邊的值定義了過濾器鏈 - 一個逗號分隔的有序Servlet過濾器列表,它會針對給出的路徑進行執行。每個過濾器都是普通的Servlet過濾器,你看到的上面的過濾器名字(anon,user,perms,authc)是Shiro內置的安全相關的特殊過濾器。你可以搭配這些安全過濾器來創建高度定制的安全體驗。你還可以指定任何其他現有的Servlet過濾器。
相比起使用web.xml,在其中先定義過濾器塊,然后定義單獨分離的過濾器模式塊,這種方式帶來的好處有多少?采用Shiro的方法,可以很容易就準確知道針對給定匹配路徑執行的過濾器鏈。如果想這么做,你可以在web.xml中僅定義Shiro Filter,在shiro.ini中定義所有其他的過濾器和過濾器鏈,這要比web.xml簡潔得多,而且更容易理解過濾器鏈定義機制。即使不使用Shiro的任何安全特性,單憑這樣小小的方便之處,也值得讓你使用Shiro。
JSP標簽庫
Shiro還提供了JSP標簽庫,允許你根據當前Subject的狀態控制JSP頁面的輸出。一個有用的常見示例是在用戶登錄后顯示“Hello <username>"文本。但若是匿名用戶,你可能想要顯示其他內容,如換而顯示“Hello! Register Today!”。清單16顯示了如何使用Shiro的JSP標簽實現這個示例:
清單16. JSP 標簽庫示例
<%@ taglib prefix="shiro"
uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
<!-- shiro:principal打印出了Subject的主當事人 - 在這個示例中,就是用戶名: -->
<shiro:principal/>!
</shiro:user>
<shiro:guest>
<!-- 沒有登錄 - 就認為是Guest。顯示注冊鏈接: -->
! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p>
除了上面例子用到的標簽,還有其他標簽可以讓你根據用戶屬于(或不屬于)的角色,分配(或未分配)的權限,是否已認證,是否來自“記住我”服務的記憶,或是匿名訪客,包含輸出。
Shiro還支持其他許多Web特性,如簡單的“記住我”服務,REST和BASIC認證。當然,如果想使用Shiro原生的企業會話,它還提供透明的HttpSession支持。參見Apache Shiro Web文檔可以了解更多內容。
Web會話管理
最后值得一提的是Shiro在Web環境中對會話的支持。
缺省Http會話
對于Web應用,Shiro缺省將使用我們習以為常的Servlet容器會話作為其會話基礎設施。即,當你調用subject.getSession()和subject.getSession(boolean)方法時,Shiro會返回Servlet容器的HttpSession實例支持的Session實例。這種方式的曼妙之處在于調用subject.getSession()的業務層代碼會跟一個Shiro Session實例交互 - 還沒有“認識”到它正跟一個基于Web的HttpSession打交道。這在維護架構層之間的清晰隔離時,是一件非常好的事情。
Web層中Shiro的原生會話
如果你由于需要Shiro的企業級會話特性(如容器無關的集群)而打開了Shiro的原生會話管理,你當然希望HttpServletRequest.getSession()和HttpSession API能和“原生”會話協作,而非Servlet容器會話。如果你不得不重構所有使用HttpServletRequest和HttpSession API的代碼,使用Shiro的Session API來替換,這將非常令人沮喪。Shiro當然從來不會期望你這么做。相反,Shiro完整實現了Servlet規范中的Session部分以在Web應用中支持原生會話。這意味著,不管何時你使用相應的HttpServletRequest或HttpSession方法調用,Shiro都會將這些調用委托給內部的原生會話API。結果,你無需修改Web代碼,即便是你正在使用Shiro的‘原生’企業會話管理 - 確實是一個非常方便(且必要)的特性。
附加特性
Apache Shiro框架還包含有對保護Java應用非常有用的其他特性,如:
- 為維持跨線程的Suject提供了線程和并發支持(支持Executor和ExecutorService);
- 為了將執行邏輯作為一種特殊的Subject,支持Callable和Runnable接口;
- 為了表現為另一個Subject的身份,支持“Run As”(比如,在管理應用中有用);
- 支持測試工具,這樣可以很容易的對Shiro的安全代碼進行單元測試和集成測試。
框架局限
常識告訴我們,Apache Shiro不是“銀彈” - 它不能毫不費力的解決所有安全問題。如下是Shiro還未解決,但是值得知道的:
-
虛擬機級別的問題:Apache Shiro當前還未處理虛擬機級別的安全,比如基于訪問控制策略,阻止類加載器中裝入某個類。然而,Shiro集成現有的JVM安全操作并非白日做夢 - 只是沒人給項目貢獻這方面的工作。
-
多階段認證:目前,Shiro不支持“多階段”認證,即用戶可能通過一種機制登錄,當被要求再次登錄時,使用另一種機制登錄。這在基于Shiro的應用中已經實現,但是通過應用預先收集所有必需信息再跟Shiro交互。這個功能在Shiro的未來版本中非常有可能得到支持。
-
Realm寫操作:目前所有Realm實現都支持“讀”操作來獲取驗證和授權數據以執行登錄和訪問控制。諸如創建用戶帳戶、組和角色或與用戶相關的角色組和權限這類“寫”操作還不支持。這是因為支持這些操作的應用數據模型變化太大,很難為所有的Shiro用戶強制定義“寫”API。
未來的特性
Apache Shiro社區每天都在壯大,借此,Shiro的特性亦是如此。在即將發布的版本中,你可能會看到:
- 更干凈的Web過濾機制,無需子類化就可支持更多的插件式過濾器。
- 更多可插拔的缺省Realm實現,優先采用組合而非繼承。你可以插入查找認證和授權數據的組件,無需實現為Shiro Realm的子類;
- 強健的OpenID和OAuth(可能是混合)客戶端支持;
- 支持Captcha;
- 針對純無狀態應用的配置簡化(如,許多REST環境);
- 通過請求/響應協議進行多階段認證;
- 通過AuthorizationRequest進行粗粒度的授權;
- 針對安全斷言查詢的ANTLR語法(比如,(‘role(admin) && (guest || !group(developer))’)
總結
Apache Shiro是一個功能齊全、健壯、通用的Java安全框架,你可以用其為你的應用護航。通過簡化應用安全的四個領域,即認證、授權、會話管理和加密,在真實應用中,應用安全能更容易被理解和實現。Shiro的簡單架構和兼容JavaBean使其幾乎能夠在任何環境下配置和使用。附加的Web支持和輔助功能,比如多線程和測試支持,讓這個框架為應用安全提供了“一站式”服務。Apache Shiro開發團隊將繼續前進,精煉代碼庫和支持社區。隨著持續被開源和商業應用采納,可以預期Shiro會繼續發展壯大。
資源