什么是Apache Shiro?
Apache Shiro(發(fā)音為“shee-roh”,日語“堡壘(Castle)”的意思)是一個(gè)強(qiáng)大易用的Java安全框架,提供了認(rèn)證、授權(quán)、加密和會(huì)話管理功能,可為任何應(yīng)用提供安全保障 - 從命令行應(yīng)用、移動(dòng)應(yīng)用到大型網(wǎng)絡(luò)及企業(yè)應(yīng)用。 Shiro為解決下列問題(我喜歡稱它們?yōu)閼?yīng)用安全的四要素)提供了保護(hù)應(yīng)用的API:
- 認(rèn)證 - 用戶身份識(shí)別,常被稱為用戶“登錄”;
- 授權(quán) - 訪問控制;
- 密碼加密 - 保護(hù)或隱藏?cái)?shù)據(jù)防止被偷窺;
- 會(huì)話管理 - 每用戶相關(guān)的時(shí)間敏感的狀態(tài)。
Shiro還支持一些輔助特性,如Web應(yīng)用安全、單元測(cè)試和多線程,它們的存在強(qiáng)化了上面提到的四個(gè)要素。
為何要?jiǎng)?chuàng)建Apache Shiro?
對(duì)于一個(gè)框架來講,使其有存在價(jià)值的最好例證就是有讓你去用它的原因,它應(yīng)該能完成一些別人無法做到的事情。要理解這一點(diǎn),需要了解Shiro的歷史以及創(chuàng)建它時(shí)的其他替代方法。
在2008年加入Apache軟件基金會(huì)之前,Shiro已經(jīng)5歲了,之前它被稱為JSecurity項(xiàng)目,始于2003年初。當(dāng)時(shí),對(duì)于Java應(yīng)用開發(fā)人員而言,沒有太多的通用安全替代方案 - 我們被Java認(rèn)證/授權(quán)服務(wù)(或稱為JAAS)緊緊套牢了。
JAAS有太多的缺點(diǎn) - 盡管它的認(rèn)證功能尚可忍受,但授權(quán)方面卻顯得拙劣,用起來令人沮喪。
此外,JAAS跟虛擬機(jī)層面的安全問題關(guān)系非常緊密,如判斷JVM中是否允許裝入一個(gè)類。作為應(yīng)用開發(fā)者,我更關(guān)心應(yīng)用最終用戶能做什么,而不是我的代碼在JVM中能做什么。
由于當(dāng)時(shí)正從事應(yīng)用開發(fā),我也需要一個(gè)干凈、容器無關(guān)的會(huì)話機(jī)制。在當(dāng)時(shí),“這場(chǎng)游戲”中唯一可用的會(huì)話是HttpSessions,它需要Web容器;或是EJB 2.1里的有狀態(tài)會(huì)話Bean,這又要EJB容器。而我想要的一個(gè)與容器脫鉤、可用于任何我選擇的環(huán)境中的會(huì)話。
最后就是加密問題。有時(shí),我們需要保證數(shù)據(jù)安全,但是Java密碼架構(gòu)(Java Cryptography Architecture)讓人難以理解,除非你是密碼學(xué)專家。
API里到處都是Checked Exception,用起來很麻煩。我需要一個(gè)干凈、開箱即用的解決方案,可以在需要時(shí)方便地對(duì)數(shù)據(jù)加密/解密。于是,縱觀2003年初的安全狀況,你會(huì)很快意識(shí)到還沒有一個(gè)大一統(tǒng)的框架滿足所有上述需求。
有鑒于此,JSecurity(即之后的Apache Shiro)誕生了。
今天,你為何愿意使用Apache Shiro?
從2003年至今,框架選擇方面的情況已經(jīng)改變了不少,但今天仍有令人信服的理由讓你選擇Shiro。其實(shí)理由相當(dāng)多,Apache Shiro:
- 易于使用 - 易用性是這個(gè)項(xiàng)目的最終目標(biāo)。應(yīng)用安全有可能會(huì)非常讓人糊涂,令人沮喪,并被認(rèn)為是“必要之惡”【譯注:比喻應(yīng)用安全方面的編程?!?。若是能讓它簡(jiǎn)化到新手都能很快上手,那它將不再是一種痛苦了。
- 廣泛性 - 沒有其他安全框架可以達(dá)到Apache Shiro宣稱的廣度,它可以為你的安全需求提供“一站式”服務(wù)。
- 靈活性 - Apache Shiro可以工作在任何應(yīng)用環(huán)境中。雖然它工作在Web、EJB和IoC環(huán)境中,但它并不依賴這些環(huán)境。Shiro既不強(qiáng)加任何規(guī)范,也無需過多依賴。
- Web能力 - Apache Shiro對(duì)Web應(yīng)用的支持很神奇,允許你基于應(yīng)用URL和Web協(xié)議(如REST)創(chuàng)建靈活的安全策略,同時(shí)還提供了一套控制頁面輸出的JSP標(biāo)簽庫。
- 可插拔 - Shiro干凈的API和設(shè)計(jì)模式使它可以方便地與許多的其他框架和應(yīng)用進(jìn)行集成。你將看到Shiro可以與諸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin這類第三方框架無縫集成。
- 支持 - Apache Shiro是Apache軟件基金會(huì)成員,這是一個(gè)公認(rèn)為了社區(qū)利益最大化而行動(dòng)的組織。項(xiàng)目開發(fā)和用戶組都有隨時(shí)愿意提供幫助的友善成員。像Katasoft這類商業(yè)公司,還可以給你提供需要的專業(yè)支持和服務(wù)。
誰在用Shiro?
Shiro及其前身JSecurity已被各種規(guī)模和不同行業(yè)的公司項(xiàng)目采用多年。
自從成為Apache軟件基金會(huì)的頂級(jí)項(xiàng)目后,站點(diǎn)流量和使用呈持續(xù)增長(zhǎng)態(tài)勢(shì)。
許多開源社區(qū)也正在用Shiro,這里有些例子如Spring,Grails,Wicket,Tapestry,Tynamo,Mule和Vaadin。如Katasoft,Sonatype,MuleSoft這樣的商業(yè)公司,一家大型社交網(wǎng)絡(luò)和多家紐約商業(yè)銀行都在使用Shiro來保護(hù)他們的商業(yè)軟件和站點(diǎn)。
核心概念:Subject,SecurityManager和Realms
既然已經(jīng)描述了Shiro的好處,那就讓我們看看它的API,好讓你能夠有個(gè)感性認(rèn)識(shí)。Shiro架構(gòu)有三個(gè)主要概念 - Subject,SecurityManager和Realms。
Subject
在考慮應(yīng)用安全時(shí),你最常問的問題可能是“當(dāng)前用戶是誰?”或“當(dāng)前用戶允許做X嗎?”。
當(dāng)我們寫代碼或設(shè)計(jì)用戶界面時(shí),問自己這些問題很平常:應(yīng)用通常都是基于用戶故事構(gòu)建的,并且你希望功能描述(和安全)是基于每個(gè)用戶的。所以,對(duì)于我們而言,考慮應(yīng)用安全的最自然方式就是基于當(dāng)前用戶。
Shiro的API用它的Subject概念從根本上體現(xiàn)了這種思考方式。
Subject一詞是一個(gè)安全術(shù)語,其基本意思是“當(dāng)前的操作用戶”。
稱之為“用戶”并不準(zhǔn)確,因?yàn)?#8220;用戶”一詞通常跟人相關(guān)。
在安全領(lǐng)域,術(shù)語“Subject”可以是人,也可以是第三方進(jìn)程、后臺(tái)帳戶(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%的事情,如登錄、登出、訪問會(huì)話、執(zhí)行授權(quán)檢查等 - 稍后還會(huì)看到更多。這里的關(guān)鍵點(diǎn)是Shiro的API非常直觀,因?yàn)樗从沉碎_發(fā)者以‘每個(gè)用戶’思考安全控制的自然趨勢(shì)。同時(shí),在代碼的任何地方都能很輕松地訪問Subject,允許在任何需要的地方進(jìn)行安全操作。
SecurityManager
Subject的“幕后”推手是SecurityManager。Subject代表了當(dāng)前用戶的安全操作,SecurityManager則管理
所有用戶的安全操作。
它是Shiro框架的核心,充當(dāng)“保護(hù)傘”,引用了多個(gè)內(nèi)部嵌套安全組件,它們形成了對(duì)象圖。
但是,一旦SecurityManager及其內(nèi)部對(duì)象圖配置好,它就會(huì)退居幕后,應(yīng)用開發(fā)人員幾乎把他們的所有時(shí)間都花在Subject API調(diào)用上。
那么,如何設(shè)置SecurityManager呢?嗯,這要看應(yīng)用的環(huán)境。
例如,Web應(yīng)用通常會(huì)在Web.xml中指定一個(gè)Shiro Servlet Filter,這會(huì)創(chuàng)建SecurityManager實(shí)例,如果你運(yùn)行的是一個(gè)獨(dú)立應(yīng)用,你需要用其他配置方式,但有很多配置選項(xiàng)。一個(gè)應(yīng)用幾乎總是只有一個(gè)SecurityManager實(shí)例。
它實(shí)際是應(yīng)用的Singleton(盡管不必是一個(gè)
靜態(tài)Singleton)。跟Shiro里的幾乎所有組件一樣,SecurityManager的缺省實(shí)現(xiàn)是
POJO,而且可用POJO兼容的任何配置機(jī)制進(jìn)行配置 - 普通的Java代碼、Spring XML、YAML、.properties和.ini文件等。
基本來講,能夠?qū)嵗惡驼{(diào)用JavaBean兼容方法的任何配置形式都可使用。為此,Shiro借助基于文本的
INI配置提供了一個(gè)缺省的“公共”解決方案。
INI易于閱讀、使用簡(jiǎn)單并且需要極少依賴。你還能看到,只要簡(jiǎn)單地理解對(duì)象導(dǎo)航,INI可被有效地用于配置像SecurityManager那樣簡(jiǎn)單的對(duì)象圖。
注意,Shiro還支持Spring XML配置及其他方式,但這里只我們只討論INI。下列清單2列出了基于INI的Shiro最簡(jiǎn)配置:
清單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實(shí)例的INI配置例子。
有兩個(gè)INI段落:[main]和[users]. [main]段落是配置SecurityManager對(duì)象及其使用的其他任何對(duì)象(如Realms)的地方。
在示例中,我們看到配置了兩個(gè)對(duì)象:
- cm對(duì)象,是Shiro的HashedCredentialsMatcher類實(shí)例。如你所見,cm實(shí)例的各屬性是通過“嵌套點(diǎn)”語法進(jìn)行配置的 - 在清單3中可以看到IniSecurityManagerFactory使用的慣例,這種方法代表了對(duì)象圖導(dǎo)航和屬性設(shè)置。
- iniRealm對(duì)象,它被SecurityManager用來表示以INI格式定義的用戶帳戶。
[users]段落是指定用戶帳戶靜態(tài)列表的地方 - 為簡(jiǎn)單應(yīng)用或測(cè)試提供了方便。
就介紹而言,詳細(xì)了解每個(gè)段落的細(xì)節(jié)并不是重點(diǎn)。
相反,看到INI配置是一種配置Shiro的簡(jiǎn)單方式才是關(guān)鍵。關(guān)于INI配置的更多細(xì)節(jié),請(qǐng)參見
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實(shí)例(使用Shiro的工廠概念,它表述了工廠方法設(shè)計(jì)模式);
- 使應(yīng)用可訪問SecurityManager Singleton。在這個(gè)簡(jiǎn)單示例中,我們將它設(shè)置為VM靜態(tài)Singleton,但這通常不是必須的 - 你的應(yīng)用配置機(jī)制可以決定你是否需要使用靜態(tài)存儲(chǔ)。
Realms
Shiro的第三個(gè)也是最后一個(gè)概念是
Realm。
Realm充當(dāng)了Shiro與應(yīng)用安全數(shù)據(jù)間的“橋梁”或者“連接器”。也就是說,當(dāng)切實(shí)與像用戶帳戶這類安全相關(guān)數(shù)據(jù)進(jìn)行交互,執(zhí)行認(rèn)證(登錄)和授權(quán)(訪問控制)時(shí),Shiro會(huì)從應(yīng)用配置的Realm中查找很多內(nèi)容。
從這個(gè)意義上講,Realm實(shí)質(zhì)上是一個(gè)安全相關(guān)的
DAO:它封裝了數(shù)據(jù)源的連接細(xì)節(jié),并在需要時(shí)將相關(guān)數(shù)據(jù)提供給Shiro。
當(dāng)配置Shiro時(shí),你必須至少指定一個(gè)Realm,用于認(rèn)證和(或)授權(quán)。配置多個(gè)Realm是可以的,但是至少需要一個(gè)。 Shiro內(nèi)置了可以連接大量安全數(shù)據(jù)源(又名目錄)的Realm,如LDAP、關(guān)系數(shù)據(jù)庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數(shù)據(jù)源的自己的Realm實(shí)現(xiàn)。
下面的清單4是通過INI配置Shiro使用LDAP目錄作為應(yīng)用Realm的示例。
清單4. Realm配置示例片段:連接存儲(chǔ)用戶數(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)了解如何建立一個(gè)基本的Shiro環(huán)境,下面讓我們來討論,作為一名開發(fā)者該如何使用這個(gè)框架。
認(rèn)證
認(rèn)證是核實(shí)用戶身份的過程。也就是說,當(dāng)用戶使用應(yīng)用進(jìn)行認(rèn)證時(shí),他們就在證明他們就是自己所說的那個(gè)人。有時(shí)這也理解為“登錄”。它是一個(gè)典型的三步驟過程。
- 收集用戶的身份信息,稱為當(dāng)事人(principal),以及身份的支持證明,稱為證書(Credential)。
- 將當(dāng)事人和證書提交給系統(tǒng)。
- 如果提交的證書與系統(tǒng)期望的該用戶身份(當(dāng)事人)匹配,該用戶就被認(rèn)為是經(jīng)過認(rèn)證的,反之則被認(rèn)為未經(jīng)認(rèn)證的。
這個(gè)過程的常見例子是大家都熟悉的“用戶/密碼”組合。多數(shù)用戶在登錄軟件系統(tǒng)時(shí),通常提供自己的用戶名(當(dāng)事人)和支持他們的密碼(證書)。
如果存儲(chǔ)在系統(tǒng)中的密碼(或密碼表示)與用戶提供的匹配,他們就被認(rèn)為通過認(rèn)證。
Shiro以簡(jiǎn)單直觀的方式支持同樣的流程。
正如我們前面所說,Shiro有一個(gè)以Subject為中心的API - 幾乎你想要用Shiro在運(yùn)行時(shí)完成的所有事情都能通過與當(dāng)前執(zhí)行的Subject進(jìn)行交互而達(dá)成。
因此,要登錄Subject,只需要簡(jiǎn)單地調(diào)用它的login方法,傳入表示被提交當(dāng)事人和證書(在這種情況下,就是用戶名和密碼)的AuthenticationToken實(shí)例。示例如清單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很容易地就反映了這個(gè)常見流程。
你將會(huì)在所有的Subject操作中繼續(xù)看到這種簡(jiǎn)單風(fēng)格。
在調(diào)用了login方法后,SecurityManager會(huì)收到AuthenticationToken,并將其發(fā)送給已配置的Realm,執(zhí)行必須的認(rèn)證檢查。每個(gè)Realm都能在必要時(shí)對(duì)提交的AuthenticationTokens作出反應(yīng)。
但是如果登錄失敗了會(huì)發(fā)生什么?如果用戶提供了錯(cuò)誤密碼又會(huì)發(fā)生什么?
通過對(duì)Shiro的運(yùn)行時(shí)AuthenticationException做出反應(yīng),你可以控制失敗,參見清單6。
清單6. 控制失敗的登錄
//3. 登錄:

try
{
currentUser.login(token);

} catch (IncorrectCredentialsException ice)
{
…

} catch (LockedAccountException lae)
{
…
}
…

catch (AuthenticationException ae)
{…
}
你可以選擇捕獲AuthenticationException的一個(gè)子類,作出特定的響應(yīng),或者對(duì)任何AuthenticationException做一般性處理(例如,顯示給用戶普通的“錯(cuò)誤的用戶名或密碼”這類消息)。
選擇權(quán)在你,可以根據(jù)應(yīng)用需要做出選擇。 Subject登錄成功后,他們就被認(rèn)為是已認(rèn)證的,通常你會(huì)允許他們使用你的應(yīng)用。但是僅僅證明了一個(gè)用戶的身份并不意味著他們可以對(duì)你的應(yīng)用為所欲為。
這就引出了另一個(gè)問題,“我如何控制用戶能做或不能做哪些事情?”,決定用戶允許做哪些事情的過程被稱為
授權(quán)。下面我們將談?wù)凷hiro如何進(jìn)行授權(quán)。
授權(quán)
授權(quán)實(shí)質(zhì)上就是訪問控制 - 控制用戶能夠訪問應(yīng)用中的哪些內(nèi)容,比如資源、Web頁面等等。
多數(shù)用戶執(zhí)行訪問控制是通過使用諸如角色和權(quán)限這類概念完成的。
也就是說,通常用戶允許或不允許做的事情是根據(jù)分配給他們的角色或權(quán)限決定的。
那么,通過檢查這些角色和權(quán)限,你的應(yīng)用程序就可以控制哪些功能是可以暴露的。
如你期望的,Subject API讓你可以很容易的執(zhí)行角色和權(quán)限檢查。如清單7中的代碼片段所示:如何檢查Subject被分配了某個(gè)角色:
列表7. 角色檢查 
if ( subject.hasRole(“administrator”) )
{
//顯示‘Create User’按鈕

} else
{
//按鈕置灰?
}
如你所見,你的應(yīng)用程序可基于訪問控制檢查打開或關(guān)閉某些功能。
權(quán)限檢查是執(zhí)行授權(quán)的另一種方法。上例中的角色檢查有個(gè)很大的缺陷:你無法在運(yùn)行時(shí)增刪角色。
角色名字在這里是硬編碼,所以,如果你修改了角色名字或配置,你的代碼就會(huì)亂套!如果你需要在運(yùn)行時(shí)改變角色含義,或想要增刪角色,你必須另辟蹊徑。為此,Shiro支持了
權(quán)限(permissions)概念。
權(quán)限是功能的原始表述,如‘開門’,‘創(chuàng)建一個(gè)博文’,‘刪除‘jsmith’用戶’等。通過讓權(quán)限反映應(yīng)用的原始功能,在改變應(yīng)用功能時(shí),你只需要改變權(quán)限檢查。
進(jìn)而,你可以在運(yùn)行時(shí)按需將權(quán)限分配給角色或用戶。如清單8中,我們重寫了之前的用戶檢查,取而代之使用權(quán)限檢查。
清單8. 權(quán)限檢查 
if ( subject.isPermitted(“user:create”) )
{
//顯示‘Create User’按鈕

} else
{
//按鈕置灰?
}
這樣,任何具有“user:create”權(quán)限的角色或用戶都可以點(diǎn)擊‘Create User’按鈕,并且這些角色和指派甚至可以在運(yùn)行時(shí)改變,這給你提供了一個(gè)非常靈活的安全模型。
“user:create”字符串是一個(gè)權(quán)限字符串的例子,它遵循特定的解析慣例。
Shiro借助它的WildcardPermission支持這種開箱即用的慣例。盡管這超出了本文的范圍,你會(huì)看到在創(chuàng)建安全策略時(shí),WildcardPermission非常靈活,甚至支持像
實(shí)例級(jí)別訪問控制這樣的功能。
清單9. 實(shí)例級(jí)別的權(quán)限檢查 
if ( subject.isPermitted(“user:delete:jsmith”) )
{
//刪除‘jsmith’用戶

} else
{
//不刪除‘jsmith’
}
該例表明,你可以對(duì)你需要的單個(gè)資源進(jìn)行訪問控制,甚至深入到非常細(xì)粒度的實(shí)例級(jí)別。
如果愿意,你甚至還可以發(fā)明自己的權(quán)限語法。參見
Shiro Permission文檔可以了解更多內(nèi)容。
最后,就像使用認(rèn)證那樣,上述調(diào)用最終會(huì)轉(zhuǎn)向SecurityManager,它會(huì)咨詢Realm做出自己的訪問控制決定。
必要時(shí),還允許單個(gè)Realm同時(shí)響應(yīng)認(rèn)證和授權(quán)操作。以上就是對(duì)Shiro授權(quán)功能的簡(jiǎn)要概述。雖然多數(shù)安全框架止于授權(quán)和認(rèn)證,但Shiro提供了更多功能。下面,我們將談?wù)凷hiro的高級(jí)會(huì)話管理功能。
會(huì)話管理
在安全框架領(lǐng)域,Apache Shiro提供了一些獨(dú)特的東西:可在任何應(yīng)用或架構(gòu)層一致地使用Session API。
即,Shiro為任何應(yīng)用提供了一個(gè)會(huì)話編程范式 - 從小型后臺(tái)獨(dú)立應(yīng)用到大型集群Web應(yīng)用。這意味著,那些希望使用會(huì)話的應(yīng)用開發(fā)者,不必被迫使用Servlet或EJB容器了。
或者,如果正在使用這些容器,開發(fā)者現(xiàn)在也可以選擇使用在任何層統(tǒng)一一致的會(huì)話API,取代Servlet或EJB機(jī)制。但Shiro會(huì)話最重要的一個(gè)好處或許就是它們是獨(dú)立于容器的。
這具有微妙但非常強(qiáng)大的影響。
例如,讓我們考慮一下會(huì)話集群。對(duì)集群會(huì)話來講,支持容錯(cuò)和故障轉(zhuǎn)移有多少種容器特定的方式?Tomcat的方式與Jetty的不同,而Jetty又和Websphere不一樣,等等。
但通過Shiro會(huì)話,你可以獲得一個(gè)容器無關(guān)的集群解決方案。Shiro的架構(gòu)允許可插拔的會(huì)話數(shù)據(jù)存儲(chǔ),如企業(yè)緩存、關(guān)系數(shù)據(jù)庫、NoSQL系統(tǒng)等。
這意味著,只要配置會(huì)話集群一次,它就會(huì)以相同的方式工作,跟部署環(huán)境無關(guān) - Tomcat、Jetty、JEE服務(wù)器或者獨(dú)立應(yīng)用。不管如何部署應(yīng)用,毋須重新配置應(yīng)用。
Shiro會(huì)話的另一好處就是,如果需要,會(huì)話數(shù)據(jù)可以跨客戶端技術(shù)進(jìn)行共享。例如,Swing桌面客戶端在需要時(shí)可以參與相同的Web應(yīng)用會(huì)話中 - 如果最終用戶同時(shí)使用這兩種應(yīng)用,這樣的功能會(huì)很有用。
那你如何在任何環(huán)境中訪問Subject的會(huì)話呢?請(qǐng)看下面的示例,里面使用了Subject的兩個(gè)方法。
清單10. Subject的會(huì)話
Session session = subject.getSession();
Session session = subject.getSession(boolean create);
如你所見,這些方法在概念上等同于HttpServletRequest API。
第一個(gè)方法會(huì)返回Subject的現(xiàn)有會(huì)話,或者如果還沒有會(huì)話,它會(huì)創(chuàng)建一個(gè)新的并將之返回。
第二個(gè)方法接受一個(gè)布爾參數(shù),這個(gè)參數(shù)用于判定會(huì)話不存在時(shí)是否創(chuàng)建新會(huì)話。
一旦獲得Shiro的會(huì)話,你幾乎可以像使用HttpSession一樣使用它。Shiro團(tuán)隊(duì)覺得對(duì)于Java開發(fā)者,HttpSession API用起來太舒服了,所以我們保留了它的很多感覺。
當(dāng)然,最大的不同在于,你可以在任何應(yīng)用中使用Shiro會(huì)話,不僅限于Web應(yīng)用。清單11中顯示了這種相似性。
清單11. 會(huì)話的方法
Session session = subject.getSession();
session.getAttribute("key", someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis); 
加密
加密是隱藏或混淆數(shù)據(jù)以避免被偷窺的過程。
在加密方面,Shiro的目標(biāo)是簡(jiǎn)化并讓JDK的加密支持可用。清楚一點(diǎn)很重要,一般情況下,加密不是特定于Subject的,所以它是Shiro API的一部分,但并不特定于Subject。
你可以在任何地方使用Shiro的加密支持,甚至在不使用Subject的情況下。對(duì)于加密支持,Shiro真正關(guān)注的兩個(gè)領(lǐng)域是加密哈希(又名消息摘要)和加密密碼。下面我們來看看這兩個(gè)方面的詳細(xì)描述。
哈希
如果你曾使用過JDK的
MessageDigest類,你會(huì)立刻意識(shí)到它的使用有點(diǎn)麻煩。
MessageDigest類有一個(gè)笨拙的基于工廠的靜態(tài)方法API,它不是面向?qū)ο蟮?,并且你被迫去捕獲那些永遠(yuǎn)都不必捕獲的Checked Exceptions。
如果需要輸出十六進(jìn)制編碼或Base64編碼的消息摘要,你只有靠自己 - 對(duì)上述兩種編碼,沒有標(biāo)準(zhǔn)的JDK支持它們。Shiro用一種干凈而直觀的哈希API解決了上述問題。
打個(gè)比方,考慮比較常見的情況,使用MD5哈希一個(gè)文件,并確定該哈希的十六進(jìn)制值。被稱為‘校驗(yàn)和’,這在提供文件下載時(shí)常用到 - 用戶可以對(duì)下載文件執(zhí)行自己的MD5哈希。如果它們匹配,用戶完全可以認(rèn)定文件在傳輸過程中沒有被篡改。
不使用Shiro,你需要如下步驟才能完成上述內(nèi)容:
- 將文件轉(zhuǎn)換成字節(jié)數(shù)組。JDK中沒有干這事的,故而你需要?jiǎng)?chuàng)建一個(gè)輔助方法用于打開FileInputStream,使用字節(jié)緩存區(qū),并拋出相關(guān)的IOExceptions,等等。
- 使用MessageDigest類對(duì)字節(jié)數(shù)組進(jìn)行哈希,處理相關(guān)異常,如清單12所示。
- 將哈希后的字節(jié)數(shù)組編碼成十六進(jìn)制字符。JDK中還是沒有干這事的,你依舊需要?jiǎng)?chuàng)建另外一個(gè)輔助方法,有可能在你的實(shí)現(xiàn)中會(huì)使用位操作和位移動(dòng)。
清單12. JDK的消息摘要 
try
{
MessageDigest md = MessageDigest.getInstance("MD5");
md.digest(bytes);
byte[] hashed = md.digest();

} catch (NoSuchAlgorithmException e)
{
e.printStackTrace();
}
對(duì)于這樣簡(jiǎn)單普遍的需求,這個(gè)工作量實(shí)在太大了?,F(xiàn)在看看
Shiro是如何做同樣事情的:
String hex = new Md5Hash(myFile).toHex();
當(dāng)使用Shiro簡(jiǎn)化所有這些工作時(shí),一切都非常簡(jiǎn)單明了。完成SHA-512哈希和密碼的Base64編碼也一樣簡(jiǎn)單。
String encodedPassword = new Sha512Hash(password, salt, count).toBase64();
你可以看到Shiro對(duì)哈希和編碼簡(jiǎn)化了不少,挽救了你處理在這類問題上所消耗的腦細(xì)胞。
密碼
加密是使用密鑰對(duì)數(shù)據(jù)進(jìn)行可逆轉(zhuǎn)換的加密算法。
我們使用其保證數(shù)據(jù)的安全,尤其是傳輸或存儲(chǔ)數(shù)據(jù)時(shí),以及在數(shù)據(jù)容易被窺探的時(shí)候。如果你曾經(jīng)用過JDK的Cryptography API,特別是javax.crypto.Cipher類,你會(huì)知道它是一頭需要馴服的極其復(fù)雜的野獸。
對(duì)于初學(xué)者,每個(gè)可能的加密配置總是由一個(gè)javax.crypto.Cipher實(shí)例表示。必須進(jìn)行公鑰/私鑰加密?你得用Cipher。需要為流操作使用塊加密器(Block Cipher)?
你得用Cipher。需要?jiǎng)?chuàng)建一個(gè)AES 256位Cipher來保護(hù)數(shù)據(jù)?你得用Cipher。你懂的。
那么如何創(chuàng)建你需要的Cipher實(shí)例?您得創(chuàng)建一個(gè)非直觀、標(biāo)記分隔的加密選項(xiàng)字符串,它被稱為“轉(zhuǎn)換字符串(transformation string)”,把該字符串傳給Cipher.getInstance靜態(tài)工廠方法。
這種字符串方式的cipher選項(xiàng),并沒有類型安全以確保你正在用有效的選項(xiàng)。
這也暗示沒有JavaDoc幫你了解相關(guān)選項(xiàng)。并且,如果字符串格式組織不正確,你還需要進(jìn)一步處理Checked Exception,即便你知道配置是正確的。如你所見,使用JDK Cipher是一項(xiàng)相當(dāng)繁重的任務(wù)。
很久以前,這些技術(shù)曾經(jīng)是Java API的標(biāo)準(zhǔn),但是世事變遷,我們需要一種更簡(jiǎn)單的方法。 Shiro通過引入它的CipherService API試圖簡(jiǎn)化加密密碼的整個(gè)概念。
CipherService是多數(shù)開發(fā)者在保護(hù)數(shù)據(jù)時(shí)夢(mèng)寐以求的東西:簡(jiǎn)單、無狀態(tài)、線程安全的API,能夠在一次方法調(diào)用中對(duì)整個(gè)數(shù)據(jù)進(jìn)行加密或解密。
你所需要做的只是提供你的密鑰,就可根據(jù)需要加密或解密。如下列清單13中,使用256位AES加密:
清單13. Apache Shiro的加密API
AesCipherService cipherService = new AesCipherService();
cipherService.setKeySize(256);

//創(chuàng)建一個(gè)測(cè)試密鑰:
byte[] testKey = cipherService.generateNewKey();
//加密文件的字節(jié):
byte[] encrypted = cipherService.encrypt(fileBytes, testKey);

較之JDK的Cipher API,Shiro的示例要簡(jiǎn)單的多:
- 你可以直接實(shí)例化一個(gè)CipherService - 沒有奇怪或讓人混亂的工廠方法;
- Cipher配置選項(xiàng)可以表示成JavaBean - 兼容的getter和setter方法 - 沒有了奇怪和難以理解的“轉(zhuǎn)換字符串”;
- 加密和解密在單個(gè)方法調(diào)用中完成;
- 沒有強(qiáng)加的Checked Exception。如果愿意,可以捕獲Shiro的CryptoException。
Shiro的CipherService API還有其他好處,如同時(shí)支持基于字節(jié)數(shù)組的加密/解密(稱為“塊”操作)和基于流的加密/解密(如加密音頻或視頻)。
不必再忍受Java Cryptography帶來的痛苦。Shiro的Cryptography支持就是為了減少你在確保數(shù)據(jù)安全上付出的努力。
Web支持
最后,但并非不重要,我們將簡(jiǎn)單介紹一下Shiro的Web支持。Shiro附帶了一個(gè)幫助保護(hù)Web應(yīng)用的強(qiáng)建的Web支持模塊。對(duì)于Web應(yīng)用,安裝Shiro很簡(jiǎn)單。唯一需要做的就是在web.xml中定義一個(gè)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>
這個(gè)過濾器可以讀取上述shiro.ini配置,這樣不論什么開發(fā)環(huán)境,你都擁有了一致的配置體驗(yàn)。
一旦完成配置,Shiro Filter就會(huì)過濾每個(gè)請(qǐng)求并且確保在請(qǐng)求期間特定請(qǐng)求的Subject是可訪問的。
同時(shí)由于它過濾了每個(gè)請(qǐng)求,你可以執(zhí)行安全特定的邏輯以保證只有滿足一定標(biāo)準(zhǔn)的請(qǐng)求才被允許通過。
URL特定的Filter鏈
Shiro通過其創(chuàng)新的URL過濾器鏈功能支持安全特定的過濾規(guī)則。
它允許你為任何匹配的URL模式指定非正式的過濾器鏈。這意味著, 使用Shiro的過濾器機(jī)制,你可以很靈活的強(qiáng)制安全規(guī)則(或者規(guī)則的組合) - 其程度遠(yuǎn)遠(yuǎn)超過你單獨(dú)在web.xml中定義過濾器時(shí)所獲得的。
清單15中顯示了Shiro INI中的配置片段。
清單15. 路徑特定的Filter鏈
[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc
http://www.iteye.com/topic/1044714?page=2