讓Apache Shiro保護(hù)你的應(yīng)用
在嘗試保護(hù)你的應(yīng)用時,你是否有過挫敗感?是否覺得現(xiàn)有的Java安全解決方案難以使用,只會讓你更糊涂?本文介紹的Apache Shiro,是一個不同尋常的Java安全框架,為保護(hù)應(yīng)用提供了簡單而強大的方法。本文還解釋了Apache Shiro的項目目標(biāo)、架構(gòu)理念以及如何使用Shiro為應(yīng)用安全保駕護(hù)航。
什么是Apache Shiro?Apache Shiro(發(fā)音為“shee-roh”,日語“堡壘(Castle)”的意思)是一個強大易用的Java安全框架,提供了認(rèn)證、授權(quán)、加密和會話管理功能,可為任何應(yīng)用提供安全保障 - 從命令行應(yīng)用、移動應(yīng)用到大型網(wǎng)絡(luò)及企業(yè)應(yīng)用。
Shiro為解決下列問題(我喜歡稱它們?yōu)閼?yīng)用安全的四要素)提供了保護(hù)應(yīng)用的API:
相關(guān)廠商內(nèi)容
Windows Azure在中國正式開啟在線直付 率先在國內(nèi)落地的全球化云服務(wù),深度了解Windows Azure 利用開發(fā)測試云實現(xiàn)精益創(chuàng)業(yè)、快速迭代 Windows Azure自管理服務(wù)實現(xiàn)近乎零停機維護(hù) Windows Azure專區(qū)上線,全面了解云服務(wù) 相關(guān)贊助商
Windows Azure專區(qū)上線,全面了解云服務(wù)精彩呈現(xiàn)!
•認(rèn)證 - 用戶身份識別,常被稱為用戶“登錄”;
•授權(quán) - 訪問控制;
•密碼加密 - 保護(hù)或隱藏數(shù)據(jù)防止被偷窺;
•會話管理 - 每用戶相關(guān)的時間敏感的狀態(tài)。
Shiro還支持一些輔助特性,如Web應(yīng)用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素。
為何要創(chuàng)建Apache Shiro?
對于一個框架來講,使其有存在價值的最好例證就是有讓你去用它的原因,它應(yīng)該能完成一些別人無法做到的事情。要理解這一點,需要了解Shiro的歷史以及創(chuàng)建它時的其他替代方法。
在2008年加入Apache軟件基金會之前,Shiro已經(jīng)5歲了,之前它被稱為JSecurity項目,始于2003年初。當(dāng)時,對于Java應(yīng)用開發(fā)人員而言,沒有太多的通用安全替代方案 - 我們被Java認(rèn)證/授權(quán)服務(wù)(或稱為JAAS)緊緊套牢了。JAAS有太多的缺點 - 盡管它的認(rèn)證功能尚可忍受,但授權(quán)方面卻顯得拙劣,用起來令人沮喪。此外,JAAS跟虛擬機層面的安全問題關(guān)系非常緊密,如判斷JVM中是否允許裝入一個類。作為應(yīng)用開發(fā)者,我更關(guān)心應(yīng)用最終用戶能做什么,而不是我的代碼在JVM中能做什么。
由于當(dāng)時正從事應(yīng)用開發(fā),我也需要一個干凈、容器無關(guān)的會話機制。在當(dāng)時,“這場游戲”中唯一可用的會話是HttpSessions,它需要Web容器;或是EJB 2.1里的有狀態(tài)會話Bean,這又要EJB容器。而我想要的一個與容器脫鉤、可用于任何我選擇的環(huán)境中的會話。
最后就是加密問題。有時,我們需要保證數(shù)據(jù)安全,但是Java密碼架構(gòu)(Java Cryptography Architecture)讓人難以理解,除非你是密碼學(xué)專家。API里到處都是Checked Exception,用起來很麻煩。我需要一個干凈、開箱即用的解決方案,可以在需要時方便地對數(shù)據(jù)加密/解密。
于是,縱觀2003年初的安全狀況,你會很快意識到還沒有一個大一統(tǒng)的框架滿足所有上述需求。有鑒于此,JSecurity(即之后的Apache Shiro)誕生了。
今天,你為何愿意使用Apache Shiro?
從2003年至今,框架選擇方面的情況已經(jīng)改變了不少,但今天仍有令人信服的理由讓你選擇Shiro。其實理由相當(dāng)多,Apache Shiro:
•易于使用 - 易用性是這個項目的最終目標(biāo)。應(yīng)用安全有可能會非常讓人糊涂,令人沮喪,并被認(rèn)為是“必要之惡”【譯注:比喻應(yīng)用安全方面的編程?!俊H羰悄茏屗喕叫率侄寄芎芸焐鲜?,那它將不再是一種痛苦了。
•廣泛性 - 沒有其他安全框架可以達(dá)到Apache Shiro宣稱的廣度,它可以為你的安全需求提供“一站式”服務(wù)。
•靈活性 - Apache Shiro可以工作在任何應(yīng)用環(huán)境中。雖然它工作在Web、EJB和IoC環(huán)境中,但它并不依賴這些環(huán)境。Shiro既不強加任何規(guī)范,也無需過多依賴。
•Web能力 - Apache Shiro對Web應(yīng)用的支持很神奇,允許你基于應(yīng)用URL和Web協(xié)議(如REST)創(chuàng)建靈活的安全策略,同時還提供了一套控制頁面輸出的JSP標(biāo)簽庫。
•可插拔 - Shiro干凈的API和設(shè)計模式使它可以方便地與許多的其他框架和應(yīng)用進(jìn)行集成。你將看到Shiro可以與諸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin這類第三方框架無縫集成。
•支持 - Apache Shiro是Apache軟件基金會成員,這是一個公認(rèn)為了社區(qū)利益最大化而行動的組織。項目開發(fā)和用戶組都有隨時愿意提供幫助的友善成員。像Katasoft這類商業(yè)公司,還可以給你提供需要的專業(yè)支持和服務(wù)。
誰在用Shiro?
Shiro及其前身JSecurity已被各種規(guī)模和不同行業(yè)的公司項目采用多年。自從成為Apache軟件基金會的頂級項目后,站點流量和使用呈持續(xù)增長態(tài)勢。許多開源社區(qū)也正在用Shiro,這里有些例子如Spring,Grails,Wicket,Tapestry,Tynamo,Mule和Vaadin。
如Katasoft,Sonatype,MuleSoft這樣的商業(yè)公司,一家大型社交網(wǎng)絡(luò)和多家紐約商業(yè)銀行都在使用Shiro來保護(hù)他們的商業(yè)軟件和站點。
核心概念:Subject,SecurityManager和Realms既然已經(jīng)描述了Shiro的好處,那就讓我們看看它的API,好讓你能夠有個感性認(rèn)識。Shiro架構(gòu)有三個主要概念 - Subject,SecurityManager和Realms。
Subject在考慮應(yīng)用安全時,你最常問的問題可能是“當(dāng)前用戶是誰?”或“當(dāng)前用戶允許做X嗎?”。當(dāng)我們寫代碼或設(shè)計用戶界面時,問自己這些問題很平常:應(yīng)用通常都是基于用戶故事構(gòu)建的,并且你希望功能描述(和安全)是基于每個用戶的。所以,對于我們而言,考慮應(yīng)用安全的最自然方式就是基于當(dāng)前用戶。Shiro的API用它的Subject概念從根本上體現(xiàn)了這種思考方式。
Subject一詞是一個安全術(shù)語,其基本意思是“當(dāng)前的操作用戶”。稱之為“用戶”并不準(zhǔn)確,因為“用戶”一詞通常跟人相關(guān)。在安全領(lǐng)域,術(shù)語“Subject”可以是人,也可以是第三方進(jìn)程、后臺帳戶(Daemon Account)或其他類似事物。它僅僅意味著“當(dāng)前跟軟件交互的東西”。但考慮到大多數(shù)目的和用途,你可以把它認(rèn)為是Shiro的“用戶”概念。在代碼的任何地方,你都能輕易的獲得Shiro Subject,參見如下代碼:
清單1. 獲得Subject
import org.apache.shiro.subject.Subject;
import org.apache.shiro.SecurityUtils;
...
Subject currentUser = SecurityUtils.getSubject();
一旦獲得Subject,你就可以立即獲得你希望用Shiro為當(dāng)前用戶做的90%的事情,如登錄、登出、訪問會話、執(zhí)行授權(quán)檢查等 - 稍后還會看到更多。這里的關(guān)鍵點是Shiro的API非常直觀,因為它反映了開發(fā)者以‘每個用戶'思考安全控制的自然趨勢。同時,在代碼的任何地方都能很輕松地訪問Subject,允許在任何需要的地方進(jìn)行安全操作。
SecurityManager
Subject的“幕后”推手是SecurityManager。Subject代表了當(dāng)前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。它是Shiro框架的核心,充當(dāng)“保護(hù)傘”,引用了多個內(nèi)部嵌套安全組件,它們形成了對象圖。但是,一旦SecurityManager及其內(nèi)部對象圖配置好,它就會退居幕后,應(yīng)用開發(fā)人員幾乎把他們的所有時間都花在Subject API調(diào)用上。
那么,如何設(shè)置SecurityManager呢?嗯,這要看應(yīng)用的環(huán)境。例如,Web應(yīng)用通常會在Web.xml中指定一個Shiro Servlet Filter,這會創(chuàng)建SecurityManager實例,如果你運行的是一個獨立應(yīng)用,你需要用其他配置方式,但有很多配置選項。
一個應(yīng)用幾乎總是只有一個SecurityManager實例。它實際是應(yīng)用的Singleton(盡管不必是一個靜態(tài)Singleton)。跟Shiro里的幾乎所有組件一樣,SecurityManager的缺省實現(xiàn)是POJO,而且可用POJO兼容的任何配置機制進(jìn)行配置 - 普通的Java代碼、Spring XML、YAML、.properties和.ini文件等?;緛碇v,能夠?qū)嵗惡驼{(diào)用JavaBean兼容方法的任何配置形式都可使用。
為此,Shiro借助基于文本的INI配置提供了一個缺省的“公共”解決方案。INI易于閱讀、使用簡單并且需要極少依賴。你還能看到,只要簡單地理解對象導(dǎo)航,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實例的各屬性是通過“嵌套點”語法進(jìn)行配置的 - 在清單3中可以看到IniSecurityManagerFactory使用的慣例,這種方法代表了對象圖導(dǎo)航和屬性設(shè)置。
- iniRealm對象,它被SecurityManager用來表示以INI格式定義的用戶帳戶。
[users]段落是指定用戶帳戶靜態(tài)列表的地方 - 為簡單應(yīng)用或測試提供了方便。
就介紹而言,詳細(xì)了解每個段落的細(xì)節(jié)并不是重點。相反,看到INI配置是一種配置Shiro的簡單方式才是關(guān)鍵。關(guān)于INI配置的更多細(xì)節(jié),請參見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. 創(chuàng)建SecurityManager
SecurityManager securityManager = factory.getInstance();
//3. 使其可訪問
SecurityUtils.setSecurityManager(securityManager);在清單3的示例中,我們看到有三步:
- 裝入用來配置SecurityManager及其構(gòu)成組件的INI配置文件;
- 根據(jù)配置創(chuàng)建SecurityManager實例(使用Shiro的工廠概念,它表述了工廠方法設(shè)計模式);
- 使應(yīng)用可訪問SecurityManager Singleton。在這個簡單示例中,我們將它設(shè)置為VM靜態(tài)Singleton,但這通常不是必須的 - 你的應(yīng)用配置機制可以決定你是否需要使用靜態(tài)存儲。
Realms
Shiro的第三個也是最后一個概念是Realm。Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”。也就是說,當(dāng)切實與像用戶帳戶這類安全相關(guān)數(shù)據(jù)進(jìn)行交互,執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問控制)時,Shiro會從應(yīng)用配置的Realm中查找很多內(nèi)容。
從這個意義上講,Realm實質(zhì)上是一個安全相關(guān)的DAO:它封裝了數(shù)據(jù)源的連接細(xì)節(jié),并在需要時將相關(guān)數(shù)據(jù)提供給Shiro。當(dāng)配置Shiro時,你必須至少指定一個Realm,用于認(rèn)證和(或)授權(quán)。配置多個Realm是可以的,但是至少需要一個。
Shiro內(nèi)置了可以連接大量安全數(shù)據(jù)源(又名目錄)的Realm,如LDAP、關(guān)系數(shù)據(jù)庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數(shù)據(jù)源的自己的Realm實現(xiàn)。下面的清單4是通過INI配置Shiro使用LDAP目錄作為應(yīng)用Realm的示例。
清單4. Realm配置示例片段:連接存儲用戶數(shù)據(jù)的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
既然已經(jīng)了解如何建立一個基本的Shiro環(huán)境,下面讓我們來討論,作為一名開發(fā)者該如何使用這個框架。
認(rèn)證認(rèn)證是核實用戶身份的過程。也就是說,當(dāng)用戶使用應(yīng)用進(jìn)行認(rèn)證時,他們就在證明他們就是自己所說的那個人。有時這也理解為“登錄”。它是一個典型的三步驟過程。
1.收集用戶的身份信息,稱為當(dāng)事人(principal),以及身份的支持證明,稱為證書(Credential)。
2.將當(dāng)事人和證書提交給系統(tǒng)。
3.如果提交的證書與系統(tǒng)期望的該用戶身份(當(dāng)事人)匹配,該用戶就被認(rèn)為是經(jīng)過認(rèn)證的,反之則被認(rèn)為未經(jīng)認(rèn)證的。
這個過程的常見例子是大家都熟悉的“用戶/密碼”組合。多數(shù)用戶在登錄軟件系統(tǒng)時,通常提供自己的用戶名(當(dāng)事人)和支持他們的密碼(證書)。如果存儲在系統(tǒng)中的密碼(或密碼表示)與用戶提供的匹配,他們就被認(rèn)為通過認(rèn)證。
Shiro以簡單直觀的方式支持同樣的流程。正如我們前面所說,Shiro有一個以Subject為中心的API - 幾乎你想要用Shiro在運行時完成的所有事情都能通過與當(dāng)前執(zhí)行的Subject進(jìn)行交互而達(dá)成。因此,要登錄Subject,只需要簡單地調(diào)用它的login方法,傳入表示被提交當(dāng)事人和證書(在這種情況下,就是用戶名和密碼)的AuthenticationToken實例。示例如清單5中所示:
清單5. Subject登錄
//1. 接受提交的當(dāng)事人和證書:
AuthenticationToken token =
new UsernamePasswordToken(username, password);
//2. 獲取當(dāng)前Subject:
Subject currentUser = SecurityUtils.getSubject();
//3. 登錄:
currentUser.login(token);
你可以看到,Shiro的API很容易地就反映了這個常見流程。你將會在所有的Subject操作中繼續(xù)看到這種簡單風(fēng)格。在調(diào)用了login方法后,SecurityManager會收到AuthenticationToken,并將其發(fā)送給已配置的Realm,執(zhí)行必須的認(rèn)證檢查。每個Realm都能在必要時對提交的AuthenticationTokens作出反應(yīng)。但是如果登錄失敗了會發(fā)生什么?如果用戶提供了錯誤密碼又會發(fā)生什么?通過對Shiro的運行時AuthenticationException做出反應(yīng),你可以控制失敗,參見清單6。
清單6. 控制失敗的登錄
//3. 登錄:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
…
} catch (LockedAccountException lae) {
…
}
…
catch (AuthenticationException ae) {…
}
你可以選擇捕獲AuthenticationException的一個子類,作出特定的響應(yīng),或者對任何AuthenticationException做一般性處理(例如,顯示給用戶普通的“錯誤的用戶名或密碼”這類消息)。選擇權(quán)在你,可以根據(jù)應(yīng)用需要做出選擇。
Subject登錄成功后,他們就被認(rèn)為是已認(rèn)證的,通常你會允許他們使用你的應(yīng)用。但是僅僅證明了一個用戶的身份并不意味著他們可以對你的應(yīng)用為所欲為。這就引出了另一個問題,“我如何控制用戶能做或不能做哪些事情?”,決定用戶允許做哪些事情的過程被稱為授權(quán)。下面我們將談?wù)凷hiro如何進(jìn)行授權(quán)。
授權(quán)授權(quán)實質(zhì)上就是訪問控制 - 控制用戶能夠訪問應(yīng)用中的哪些內(nèi)容,比如資源、Web頁面等等。多數(shù)用戶執(zhí)行訪問控制是通過使用諸如角色和權(quán)限這類概念完成的。也就是說,通常用戶允許或不允許做的事情是根據(jù)分配給他們的角色或權(quán)限決定的。那么,通過檢查這些角色和權(quán)限,你的應(yīng)用程序就可以控制哪些功能是可以暴露的。如你期望的,Subject API讓你可以很容易的執(zhí)行角色和權(quán)限檢查。如清單7中的代碼片段所示:如何檢查Subject被分配了某個角色:
列表7. 角色檢查
if ( subject.hasRole(“administrator”) ) {
//顯示‘Create User'按鈕
} else {
//按鈕置灰?
}
如你所見,你的應(yīng)用程序可基于訪問控制檢查打開或關(guān)閉某些功能。
權(quán)限檢查是執(zhí)行授權(quán)的另一種方法。上例中的角色檢查有個很大的缺陷:你無法在運行時增刪角色。角色名字在這里是硬編碼,所以,如果你修改了角色名字或配置,你的代碼就會亂套!如果你需要在運行時改變角色含義,或想要增刪角色,你必須另辟蹊徑。
為此,Shiro支持了權(quán)限(permissions)概念。權(quán)限是功能的原始表述,如‘開門',‘創(chuàng)建一個博文',‘刪除‘jsmith'用戶'等。通過讓權(quán)限反映應(yīng)用的原始功能,在改變應(yīng)用功能時,你只需要改變權(quán)限檢查。進(jìn)而,你可以在運行時按需將權(quán)限分配給角色或用戶。
如清單8中,我們重寫了之前的用戶檢查,取而代之使用權(quán)限檢查。
清單8. 權(quán)限檢查
if ( subject.isPermitted(“user:create”) ) {
//顯示‘Create User'按鈕
} else {
//按鈕置灰?
}
這樣,任何具有“user:create”權(quán)限的角色或用戶都可以點擊‘Create User'按鈕,并且這些角色和指派甚至可以在運行時改變,這給你提供了一個非常靈活的安全模型。
“user:create”字符串是一個權(quán)限字符串的例子,它遵循特定的解析慣例。Shiro借助它的WildcardPermission支持這種開箱即用的慣例。盡管這超出了本文的范圍,你會看到在創(chuàng)建安全策略時,WildcardPermission非常靈活,甚至支持像實例級別訪問控制這樣的功能。
清單9. 實例級別的權(quán)限檢查
if ( subject.isPermitted(“user:delete:jsmith”) ) {
//刪除‘jsmith'用戶
} else {
//不刪除‘jsmith'
}
該例表明,你可以對你需要的單個資源進(jìn)行訪問控制,甚至深入到非常細(xì)粒度的實例級別。如果愿意,你甚至還可以發(fā)明自己的權(quán)限語法。參見Shiro Permission文檔可以了解更多內(nèi)容。最后,就像使用認(rèn)證那樣,上述調(diào)用最終會轉(zhuǎn)向SecurityManager,它會咨詢Realm做出自己的訪問控制決定。必要時,還允許單個Realm同時響應(yīng)認(rèn)證和授權(quán)操作。
以上就是對Shiro授權(quán)功能的簡要概述。雖然多數(shù)安全框架止于授權(quán)和認(rèn)證,但Shiro提供了更多功能。下面,我們將談?wù)凷hiro的高級會話管理功能。
會話管理在安全框架領(lǐng)域,Apache Shiro提供了一些獨特的東西:可在任何應(yīng)用或架構(gòu)層一致地使用Session API。即,Shiro為任何應(yīng)用提供了一個會話編程范式 - 從小型后臺獨立應(yīng)用到大型集群Web應(yīng)用。這意味著,那些希望使用會話的應(yīng)用開發(fā)者,不必被迫使用Servlet或EJB容器了?;蛘撸绻谑褂眠@些容器,開發(fā)者現(xiàn)在也可以選擇使用在任何層統(tǒng)一一致的會話API,取代Servlet或EJB機制。
但Shiro會話最重要的一個好處或許就是它們是獨立于容器的。這具有微妙但非常強大的影響。例如,讓我們考慮一下會話集群。對集群會話來講,支持容錯和故障轉(zhuǎn)移有多少種容器特定的方式?Tomcat的方式與Jetty的不同,而Jetty又和Websphere不一樣,等等。但通過Shiro會話,你可以獲得一個容器無關(guān)的集群解決方案。Shiro的架構(gòu)允許可插拔的會話數(shù)據(jù)存儲,如企業(yè)緩存、關(guān)系數(shù)據(jù)庫、NoSQL系統(tǒng)等。這意味著,只要配置會話集群一次,它就會以相同的方式工作,跟部署環(huán)境無關(guān) - Tomcat、Jetty、JEE服務(wù)器或者獨立應(yīng)用。不管如何部署應(yīng)用,毋須重新配置應(yīng)用。
Shiro會話的另一好處就是,如果需要,會話數(shù)據(jù)可以跨客戶端技術(shù)進(jìn)行共享。例如,Swing桌面客戶端在需要時可以參與相同的Web應(yīng)用會話中 - 如果最終用戶同時使用這兩種應(yīng)用,這樣的功能會很有用。那你如何在任何環(huán)境中訪問Subject的會話呢?請看下面的示例,里面使用了Subject的兩個方法。
清單10. Subject的會話
Session session = subject.getSession();
Session session = subject.getSession(boolean create);
如你所見,這些方法在概念上等同于HttpServletRequest API。第一個方法會返回Subject的現(xiàn)有會話,或者如果還沒有會話,它會創(chuàng)建一個新的并將之返回。第二個方法接受一個布爾參數(shù),這個參數(shù)用于判定會話不存在時是否創(chuàng)建新會話。一旦獲得Shiro的會話,你幾乎可以像使用HttpSession一樣使用它。Shiro團(tuán)隊覺得對于Java開發(fā)者,HttpSession API用起來太舒服了,所以我們保留了它的很多感覺。當(dāng)然,最大的不同在于,你可以在任何應(yīng)用中使用Shiro會話,不僅限于Web應(yīng)用。清單11中顯示了這種相似性。
清單11. 會話的方法
Session session = subject.getSession();
session.getAttribute("key", someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis); ...
加密加密是隱藏或混淆數(shù)據(jù)以避免被偷窺的過程。在加密方面,Shiro的目標(biāo)是簡化并讓JDK的加密支持可用。
清楚一點很重要,一般情況下,加密不是特定于Subject的,所以它是Shiro API的一部分,但并不特定于Subject。你可以在任何地方使用Shiro的加密支持,甚至在不使用Subject的情況下。對于加密支持,Shiro真正關(guān)注的兩個領(lǐng)域是加密哈希(又名消息摘要)和加密密碼。下面我們來看看這兩個方面的詳細(xì)描述。
哈希如果你曾使用過JDK的MessageDigest類,你會立刻意識到它的使用有點麻煩。MessageDigest類有一個笨拙的基于工廠的靜態(tài)方法API,它不是面向?qū)ο蟮?,并且你被迫去捕獲那些永遠(yuǎn)都不必捕獲的Checked Exceptions。如果需要輸出十六進(jìn)制編碼或Base64編碼的消息摘要,你只有靠自己 - 對上述兩種編碼,沒有標(biāo)準(zhǔn)的JDK支持它們。Shiro用一種干凈而直觀的哈希API解決了上述問題。
打個比方,考慮比較常見的情況,使用MD5哈希一個文件,并確定該哈希的十六進(jìn)制值。被稱為‘校驗和',這在提供文件下載時常用到 - 用戶可以對下載文件執(zhí)行自己的MD5哈希。如果它們匹配,用戶完全可以認(rèn)定文件在傳輸過程中沒有被篡改。
不使用Shiro,你需要如下步驟才能完成上述內(nèi)容:
1.將文件轉(zhuǎn)換成字節(jié)數(shù)組。JDK中沒有干這事的,故而你需要創(chuàng)建一個輔助方法用于打開FileInputStream,使用字節(jié)緩存區(qū),并拋出相關(guān)的IOExceptions,等等。
2.使用MessageDigest類對字節(jié)數(shù)組進(jìn)行哈希,處理相關(guān)異常,如清單12所示。
3.將哈希后的字節(jié)數(shù)組編碼成十六進(jìn)制字符。JDK中還是沒有干這事的,你依舊需要創(chuàng)建另外一個輔助方法,有可能在你的實現(xiàn)中會使用位操作和位移動。
清單12. JDK的消息摘要
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.digest(bytes);
byte[] hashed = md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
對于這樣簡單普遍的需求,這個工作量實在太大了?,F(xiàn)在看看Shiro是如何做同樣事情的:
String hex = new Md5Hash(myFile).toHex();
當(dāng)使用Shiro簡化所有這些工作時,一切都非常簡單明了。完成SHA-512哈希和密碼的Base64編碼也一樣簡單。
String encodedPassword = new Sha512Hash(password, salt, count).toBase64();
你可以看到Shiro對哈希和編碼簡化了不少,挽救了你處理在這類問題上所消耗的腦細(xì)胞。
密碼加密是使用密鑰對數(shù)據(jù)進(jìn)行可逆轉(zhuǎn)換的加密算法。我們使用其保證數(shù)據(jù)的安全,尤其是傳輸或存儲數(shù)據(jù)時,以及在數(shù)據(jù)容易被窺探的時候。
如果你曾經(jīng)用過JDK的Cryptography API,特別是javax.crypto.Cipher類,你會知道它是一頭需要馴服的極其復(fù)雜的野獸。對于初學(xué)者,每個可能的加密配置總是由一個javax.crypto.Cipher實例表示。必須進(jìn)行公鑰/私鑰加密?你得用Cipher。需要為流操作使用塊加密器(Block Cipher)?你得用Cipher。需要創(chuàng)建一個AES 256位Cipher來保護(hù)數(shù)據(jù)?你得用Cipher。你懂的。
那么如何創(chuàng)建你需要的Cipher實例?您得創(chuàng)建一個非直觀、標(biāo)記分隔的加密選項字符串,它被稱為“轉(zhuǎn)換字符串(transformation string)”,把該字符串傳給Cipher.getInstance靜態(tài)工廠方法。這種字符串方式的cipher選項,并沒有類型安全以確保你正在用有效的選項。這也暗示沒有JavaDoc幫你了解相關(guān)選項。并且,如果字符串格式組織不正確,你還需要進(jìn)一步處理Checked Exception,即便你知道配置是正確的。如你所見,使用JDK Cipher是一項相當(dāng)繁重的任務(wù)。很久以前,這些技術(shù)曾經(jīng)是Java API的標(biāo)準(zhǔn),但是世事變遷,我們需要一種更簡單的方法。
Shiro通過引入它的CipherService API試圖簡化加密密碼的整個概念。CipherService是多數(shù)開發(fā)者在保護(hù)數(shù)據(jù)時夢寐以求的東西:簡單、無狀態(tài)、線程安全的API,能夠在一次方法調(diào)用中對整個數(shù)據(jù)進(jìn)行加密或解密。你所需要做的只是提供你的密鑰,就可根據(jù)需要加密或解密。如下列清單13中,使用256位AES加密:
清單13. Apache Shiro的加密API
AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);
//創(chuàng)建一個測試密鑰:
byte[] testKey = cipherService.generateNewKey();
//加密文件的字節(jié):
byte[] encrypted = cipherService.encrypt(fileBytes, testKey);
較之JDK的Cipher API,Shiro的示例要簡單的多:
•你可以直接實例化一個CipherService - 沒有奇怪或讓人混亂的工廠方法;
•Cipher配置選項可以表示成JavaBean - 兼容的getter和setter方法 - 沒有了奇怪和難以理解的“轉(zhuǎn)換字符串”;
•加密和解密在單個方法調(diào)用中完成;
•沒有強加的Checked Exception。如果愿意,可以捕獲Shiro的CryptoException。
Shiro的CipherService API還有其他好處,如同時支持基于字節(jié)數(shù)組的加密/解密(稱為“塊”操作)和基于流的加密/解密(如加密音頻或視頻)。
不必再忍受Java Cryptography帶來的痛苦。Shiro的Cryptography支持就是為了減少你在確保數(shù)據(jù)安全上付出的努力。
Web支持最后,但并非不重要,我們將簡單介紹一下Shiro的Web支持。Shiro附帶了一個幫助保護(hù)Web應(yīng)用的強建的Web支持模塊。對于Web應(yīng)用,安裝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配置,這樣不論什么開發(fā)環(huán)境,你都擁有了一致的配置體驗。一旦完成配置,Shiro Filter就會過濾每個請求并且確保在請求期間特定請求的Subject是可訪問的。同時由于它過濾了每個請求,你可以執(zhí)行安全特定的邏輯以保證只有滿足一定標(biāo)準(zhǔn)的請求才被允許通過。
URL特定的Filter鏈
Shiro通過其創(chuàng)新的URL過濾器鏈功能支持安全特定的過濾規(guī)則。它允許你為任何匹配的URL模式指定非正式的過濾器鏈。這意味著, 使用Shiro的過濾器機制,你可以很靈活的強制安全規(guī)則(或者規(guī)則的組合) - 其程度遠(yuǎn)遠(yuǎn)超過你單獨在web.xml中定義過濾器時所獲得的。清單15中顯示了Shiro INI中的配置片段。
清單15. 路徑特定的Filter鏈
[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc
如你所見,Web應(yīng)用可以使用[urls] INI段落。對于每一行,等號左邊的值表示相對上下文的Web應(yīng)用路徑。等號右邊的值定義了過濾器鏈 - 一個逗號分隔的有序Servlet過濾器列表,它會針對給出的路徑進(jìn)行執(zhí)行。每個過濾器都是普通的Servlet過濾器,你看到的上面的過濾器名字(anon,user,perms,authc)是Shiro內(nèi)置的安全相關(guān)的特殊過濾器。你可以搭配這些安全過濾器來創(chuàng)建高度定制的安全體驗。你還可以指定任何其他現(xiàn)有的Servlet過濾器。
相比起使用web.xml,在其中先定義過濾器塊,然后定義單獨分離的過濾器模式塊,這種方式帶來的好處有多少?采用Shiro的方法,可以很容易就準(zhǔn)確知道針對給定匹配路徑執(zhí)行的過濾器鏈。如果想這么做,你可以在web.xml中僅定義Shiro Filter,在shiro.ini中定義所有其他的過濾器和過濾器鏈,這要比web.xml簡潔得多,而且更容易理解過濾器鏈定義機制。即使不使用Shiro的任何安全特性,單憑這樣小小的方便之處,也值得讓你使用Shiro。
JSP標(biāo)簽庫
Shiro還提供了JSP標(biāo)簽庫,允許你根據(jù)當(dāng)前Subject的狀態(tài)控制JSP頁面的輸出。一個有用的常見示例是在用戶登錄后顯示“Hello <username>"文本。但若是匿名用戶,你可能想要顯示其他內(nèi)容,如換而顯示“Hello! Register Today!”。清單16顯示了如何使用Shiro的JSP標(biāo)簽實現(xiàn)這個示例:
清單16. JSP 標(biāo)簽庫示例
<%@ taglib prefix="shiro"
uri="http://shiro.apache.org/tags" %>
...
<p>Hello
<shiro:user>
<!-- shiro:principal打印出了Subject的主當(dāng)事人 - 在這個示例中,就是用戶名: -->
<shiro:principal/>!
</shiro:user>
<shiro:guest>
<!-- 沒有登錄 - 就認(rèn)為是Guest。顯示注冊鏈接: -->
! <a href=”register.jsp”>Register today!</a>
</shiro:guest>
</p>
除了上面例子用到的標(biāo)簽,還有其他標(biāo)簽可以讓你根據(jù)用戶屬于(或不屬于)的角色,分配(或未分配)的權(quán)限,是否已認(rèn)證,是否來自“記住我”服務(wù)的記憶,或是匿名訪客,包含輸出。
Shiro還支持其他許多Web特性,如簡單的“記住我”服務(wù),REST和BASIC認(rèn)證。當(dāng)然,如果想使用Shiro原生的企業(yè)會話,它還提供透明的HttpSession支持。參見Apache Shiro Web文檔可以了解更多內(nèi)容。
Web會話管理
最后值得一提的是Shiro在Web環(huán)境中對會話的支持。
缺省Http會話
對于Web應(yīng)用,Shiro缺省將使用我們習(xí)以為常的Servlet容器會話作為其會話基礎(chǔ)設(shè)施。即,當(dāng)你調(diào)用subject.getSession()和subject.getSession(boolean)方法時,Shiro會返回Servlet容器的HttpSession實例支持的Session實例。這種方式的曼妙之處在于調(diào)用subject.getSession()的業(yè)務(wù)層代碼會跟一個Shiro Session實例交互 - 還沒有“認(rèn)識”到它正跟一個基于Web的HttpSession打交道。這在維護(hù)架構(gòu)層之間的清晰隔離時,是一件非常好的事情。
Web層中Shiro的原生會話
如果你由于需要Shiro的企業(yè)級會話特性(如容器無關(guān)的集群)而打開了Shiro的原生會話管理,你當(dāng)然希望HttpServletRequest.getSession()和HttpSession API能和“原生”會話協(xié)作,而非Servlet容器會話。如果你不得不重構(gòu)所有使用HttpServletRequest和HttpSession API的代碼,使用Shiro的Session API來替換,這將非常令人沮喪。Shiro當(dāng)然從來不會期望你這么做。相反,Shiro完整實現(xiàn)了Servlet規(guī)范中的Session部分以在Web應(yīng)用中支持原生會話。這意味著,不管何時你使用相應(yīng)的HttpServletRequest或HttpSession方法調(diào)用,Shiro都會將這些調(diào)用委托給內(nèi)部的原生會話API。結(jié)果,你無需修改Web代碼,即便是你正在使用Shiro的‘原生'企業(yè)會話管理 - 確實是一個非常方便(且必要)的特性。
附加特性
Apache Shiro框架還包含有對保護(hù)Java應(yīng)用非常有用的其他特性,如:
- 為維持跨線程的Suject提供了線程和并發(fā)支持(支持Executor和ExecutorService);
- 為了將執(zhí)行邏輯作為一種特殊的Subject,支持Callable和Runnable接口;
- 為了表現(xiàn)為另一個Subject的身份,支持“Run As”(比如,在管理應(yīng)用中有用);
- 支持測試工具,這樣可以很容易的對Shiro的安全代碼進(jìn)行單元測試和集成測試。
框架局限
常識告訴我們,Apache Shiro不是“銀彈” - 它不能毫不費力的解決所有安全問題。如下是Shiro還未解決,但是值得知道的:
- 虛擬機級別的問題:Apache Shiro當(dāng)前還未處理虛擬機級別的安全,比如基于訪問控制策略,阻止類加載器中裝入某個類。然而,Shiro集成現(xiàn)有的JVM安全操作并非白日做夢 - 只是沒人給項目貢獻(xiàn)這方面的工作。
- 多階段認(rèn)證:目前,Shiro不支持“多階段”認(rèn)證,即用戶可能通過一種機制登錄,當(dāng)被要求再次登錄時,使用另一種機制登錄。這在基于Shiro的應(yīng)用中已經(jīng)實現(xiàn),但是通過應(yīng)用預(yù)先收集所有必需信息再跟Shiro交互。這個功能在Shiro的未來版本中非常有可能得到支持。
- Realm寫操作:目前所有Realm實現(xiàn)都支持“讀”操作來獲取驗證和授權(quán)數(shù)據(jù)以執(zhí)行登錄和訪問控制。諸如創(chuàng)建用戶帳戶、組和角色或與用戶相關(guān)的角色組和權(quán)限這類“寫”操作還不支持。這是因為支持這些操作的應(yīng)用數(shù)據(jù)模型變化太大,很難為所有的Shiro用戶強制定義“寫”API。
未來的特性
Apache Shiro社區(qū)每天都在壯大,借此,Shiro的特性亦是如此。在即將發(fā)布的版本中,你可能會看到:
- 更干凈的Web過濾機制,無需子類化就可支持更多的插件式過濾器。
- 更多可插拔的缺省Realm實現(xiàn),優(yōu)先采用組合而非繼承。你可以插入查找認(rèn)證和授權(quán)數(shù)據(jù)的組件,無需實現(xiàn)為Shiro Realm的子類;
- 強健的OpenID和OAuth(可能是混合)客戶端支持;
- 支持Captcha;
- 針對純無狀態(tài)應(yīng)用的配置簡化(如,許多REST環(huán)境);
- 通過請求/響應(yīng)協(xié)議進(jìn)行多階段認(rèn)證;
- 通過AuthorizationRequest進(jìn)行粗粒度的授權(quán);
- 針對安全斷言查詢的ANTLR語法(比如,(‘role(admin) && (guest || !group(developer))')
總結(jié)
Apache Shiro是一個功能齊全、健壯、通用的Java安全框架,你可以用其為你的應(yīng)用護(hù)航。通過簡化應(yīng)用安全的四個領(lǐng)域,即認(rèn)證、授權(quán)、會話管理和加密,在真實應(yīng)用中,應(yīng)用安全能更容易被理解和實現(xiàn)。Shiro的簡單架構(gòu)和兼容JavaBean使其幾乎能夠在任何環(huán)境下配置和使用。附加的Web支持和輔助功能,比如多線程和測試支持,讓這個框架為應(yīng)用安全提供了“一站式”服務(wù)。Apache Shiro開發(fā)團(tuán)隊將繼續(xù)前進(jìn),精煉代碼庫和支持社區(qū)。隨著持續(xù)被開源和商業(yè)應(yīng)用采納,可以預(yù)期Shiro會繼續(xù)發(fā)展壯大。
資源
- Apache Shiro的主頁。
- Shiro的下載頁面,包含面向Maven和Ant+Ivy用戶的額外信息。
- Apache Shiro的文檔頁面,包含指南和參考手冊。
- Apache Shiro演講視頻和幻燈,項目的PMC主席Les Hazlewood提供。
- 其他關(guān)于Apache Shiro的文章和幻燈
- Apache Shiro的郵件列表和論壇。
- Katasoft - 提供Apache Shiro專業(yè)支持和應(yīng)用安全產(chǎn)品的公司。
關(guān)于作者
Les Hazlewood是Apache Shiro PMC主席以及Katasoft的CTO和合伙創(chuàng)辦人,該公司是專注于應(yīng)用安全產(chǎn)品以及提供Apache Shiro專業(yè)支持的創(chuàng)業(yè)公司。作為專業(yè)Java開發(fā)人員及企業(yè)架構(gòu)師,以及之前Bloomberg,Delta Airlines和JBoss的高級角色,Les擁有10年的的經(jīng)驗。Les積極從事開源開發(fā)已有9年時間,提交或做出貢獻(xiàn)的項目有Spring框架、Hibernate、JBoss,OpenSpaces,當(dāng)然還有JSecurity,Apache Shiro的前身。Les目前居住在加州的圣馬特奧,不編程時,他會練習(xí)劍道和學(xué)習(xí)日語。
查看英文原文: Application Security With Apache Shiro
感謝胡鍵對本文的審校。
相關(guān)文章
linux/OSX中“DD”命令制作ISO鏡像操作系統(tǒng)安裝U盤的方法
這篇文章主要介紹了linux/OSX中“DD”命令制作ISO鏡像操作系統(tǒng)安裝U盤的方法,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-09-09ubuntu服務(wù)器環(huán)境下安裝python的方法
這篇文章主要介紹了ubuntu服務(wù)器環(huán)境下安裝python的方法,簡單分析了Ubuntu環(huán)境下安裝Python的相關(guān)步驟、命令、與操作注意事項,需要的朋友可以參考下2018-03-03crontab定時任務(wù)不執(zhí)行的一些原因總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于crontab定時任務(wù)不執(zhí)行的一些原因,對每種可能發(fā)生的原因都給出了解決方法,對遇到這個問題的朋友們具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01解析Linux高性能網(wǎng)絡(luò)IO和Reactor模型
網(wǎng)絡(luò)I/O,可以理解為網(wǎng)絡(luò)上的數(shù)據(jù)流。通常我們會基于socket與遠(yuǎn)端建立一條TCP或者UDP通道,然后進(jìn)行讀寫。單個socket時,使用一個線程即可高效處理;然而如果是10K個socket連接,或者更多,我們?nèi)绾巫龅礁咝阅芴幚?/div> 2021-06-06Ubuntu18.04一次性升級Python所有庫的方法步驟
這篇文章主要介紹了Ubuntu18.04一次性升級Python所有庫的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01CentOS7 LNMP+phpmyadmin環(huán)境搭建 第二篇LNMP環(huán)境搭建教程
這篇文章主要為大家詳細(xì)介紹了CentOS7 LNMP+phpmyadmin環(huán)境搭建,第二篇LNMP環(huán)境搭建教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07最新評論