# 22. Application Security With Apache Shiro 用Shiro保護你的應用安全
# 22. Application Security With Apache Shiro 用Shiro保護你的應用安全
在嘗試保護你的應用時,你是否有過挫折?是否覺得現有的 Java 安全解決方案難以使用,只會讓你更糊涂?本文介紹的 Apache Shiro,是一個不同尋常的 Java 安全框架,為保護應用提供了簡單而強大的方法。本文還解釋了 Apache Shiro 的項目目標、架構理念以及如何使用 Shiro 為應用安全保駕護航。
## What is Apache Shiro??
Apache Shiro(發音為“shee-roh”,日語“堡壘(Castle)”的意思)是一個強大易用的 Java 安全框架,提供了認證、授權、加密和會話管理功能,可為任何應用提供安全保障——從命令行應用、移動應用到大型網絡及企業應用。
Shiro 為解決下列問題提供了保護應用的API(我喜歡稱它們為應用安全的四大基石):
- **Authentication(認證):**用戶身份識別,通常被稱為用戶“登錄”
- **Authorization(授權):**訪問控制。
- **Cryptography(加密):**保護或隱藏數據防止被偷窺。
- **Session Management(會話管理):**每用戶相關的時間敏感的狀態。
Shiro 還支持一些輔助特性,如 Web 應用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素。
## Why was Apache Shiro created 為何要創建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)誕生了。
## Why would you use Apache Shiro today 今天,你為何愿意使用 Shiro?
從 2003 年至今,框架選擇方面的情況已經改變了不少,但今天仍有令人信服的理由讓你選擇 Shiro。其實理由相當多,Apache Shiro:
- **易于使用** - 易用性是這個項目的最終目標。應用安全有可能會非常讓人糊涂,令人沮喪,并被認為是“necessary evil(必要之惡)”。若是能讓它簡化到新手都能很快上手,那它將不再是一種痛苦了。
- **廣泛性** - 沒有其他安全框架可以達到 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 軟件基金會](http://www.apache.org/)成員,這是一個公認為了社區利益最大化而行動的組織。項目開發和用戶組都有隨時愿意提供幫助的友善成員。像 [Katasoft](http://www.katasoft.com/) 這類商業公司,還可以給你提供需要的專業支持和服務。
## Who’s Using Shiro 誰在用Shiro?
Shiro 及其前身 JSecurity 已被各種規模和不同行業的公司項目采用多年。自從成為 Apache 軟件基金會的頂級項目后,站點流量和使用呈持續增長態勢。許多開源社區也正在用 Shiro,這里有些例子如 Spring,Grails,Wicket,Tapestry,Tynamo,Mule和Vaadin。
如Katasoft,Sonatype,MuleSoft這樣的商業公司,一家大型社交網絡和多家紐約商業銀行都在使用 Shiro 來保護他們的商業軟件和站點。
## Core Concepts: Subject, SecurityManager, and Realms 核心概念
已經描述了Shiro的好處,那就讓我們看看它的API,好讓你能夠有個感性認識。Shiro 架構有三個主要概念 - Subject,SecurityManager和Realms。
### Subject
在考慮應用安全時,你最常問的問題可能是“當前用戶是誰?”或“當前用戶允許做什么嗎?”。當我們寫代碼或設計用戶界面時,問自己這些問題很平常:應用通常都是基于用戶故事構建的,并且你希望功能描述(和安全)是基于每個用戶的。所以,對于我們而言,考慮應用安全的最自然方式就是基于當前用戶。Shiro 的 API 用它的 Subject 概念從根本上體現了這種思考方式。
Subject 一詞是一個安全術語,其基本意思是“當前的操作用戶”。稱之為“用戶”并不準確,因為“用戶”一詞通常跟人相關。在安全領域,術語“Subject”可以是人,也可以是第三方進程、后臺帳戶(Daemon Account)或其他類似事物。它僅僅意味著“當前跟軟件交互的東西”。但考慮到大多數目的和用途,你可以把它認為是 Shiro 的“用戶”概念。在代碼的任何地方,你都能輕易的獲得 Shiro Subject,參見如下代碼:
Listing 1. Acquiring the Subject 獲得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最簡配置:
Listing 2. Configuring Shiro with INI 用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)的地方。在示例中,我們看到配置了兩個對象:
1. cm對象,是 Shiro 的 HashedCredentialsMatcher 類實例。如你所見,cm 實例的各屬性是通過“嵌套點”語法進行配置的 - 在清單3中可以看到I niSecurityManagerFactory 使用的慣例,這種方法代表了對象圖導航和屬性設置。
2. iniRealm 對象,它被 SecurityManager 用來表示以 INI 格式定義的用戶帳戶。
\[users\]段落是指定用戶帳戶靜態列表的地方 - 為簡單應用或測試提供了方便。
就介紹而言,詳細了解每個段落的細節并不是重點。相反,看到 INI 配置是一種配置 Shiro 的簡單方式才是關鍵。關于 INI 配置的更多細節,請參見 Shiro 文檔。
Listing 3. Loading shiro.ini Configuration File 加載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的示例中,我們看到有三步:
1. 加裝用來配置 SecurityManager 及其構成組件的 INI 配置文件;
2. 根據配置創建 SecurityManager 實例(使用 Shiro 的工廠概念,它表述了[工廠模式](http://en.wikipedia.org/wiki/Factory_method_pattern));
3. 使應用可訪問 SecurityManager Singleton。在這個簡單示例中,我們將它設置為 VM 靜態 Singleton,但這通常不是必須的——你的應用配置機制可以決定你是否需要使用靜態存儲。
### Realms
Realm 充當了 Shiro 與應用安全數據間的“橋梁”或者“連接器”。也就是說,當切實與像用戶帳戶這類安全相關數據進行交互,執行認證(登錄)和授權(訪問控制)時,Shiro 會從應用配置的 Realm 中查找很多內容。
從這個意義上講,Realm 實質上是一個安全相關的 [DAO](http://en.wikipedia.org/wiki/Data_access_object):它封裝了數據源的連接細節,并在需要時將相關數據提供給Shiro。當配置 Shiro 時,你必須至少指定一個Realm,用于認證和(或)授權。配置多個 Realm 是可以的,但是至少需要一個。
Shiro 內置了可以連接大量安全數據源(又名目錄)的 Realm,如LDAP、關系數據庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數據源的自己的Realm實現。下面的清單4是通過INI配置Shiro使用LDAP目錄作為應用Realm的示例。
Listing 4. Example realm configuration snippet to connect to LDAP user data store 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環境,下面讓我們來討論,作為一名開發者該如何使用這個框架。
### Authentication認證
認證是核實用戶身份的過程。也就是說,當用戶使用應用進行認證時,他們就在證明他們就是自己所說的那個人。有時這也理解為“登錄”。它是一個典型的三步驟過程。
1. 收集用戶的身份信息,稱為用戶的身份(principal),以及身份的支持證明,稱為證明(Credential)。
2. 將用戶的身份和證書提交給系統。
3. 如果提交的證書與系統期望的該用戶身份(當事人)匹配,該用戶就被認為是經過認證的,反之則被認為未經認證的。
這個過程的常見例子是大家都熟悉的“用戶/密碼”組合。多數用戶在登錄軟件系統時,通常提供自己的用戶名(用戶的身份)和支持他們的密碼(證書)。如果存儲在系統中的密碼(或密碼表示)與用戶提供的匹配,他們就被認為通過認證。
Shiro 以簡單直觀的方式支持同樣的流程。正如我們前面所說,Shiro 有一個以 Subject 為中心的API - 幾乎你想要用 Shiro 在運行時完成的所有事情都能通過與當前執行的Subject進行交互而達成。因此,要登錄Subject,只需要簡單地調用它的login方法,傳入表示被提交當事人和證書(在這種情況下,就是用戶名和密碼)的 AuthenticationToken實例。示例如清單5中所示:
Listing 5. Subject Login 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。
Listing 6. Handle Failed Login 處理失敗的登錄
```
//3. 登錄:
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
…
} catch (LockedAccountException lae) {
…
}
…
catch (AuthenticationException ae) {…
}
```
你可以選擇捕獲 AuthenticationException 的一個子類,作出特定的響應,或者對任何AuthenticationException 做一般性處理(例如,顯示給用戶普通的“錯誤的用戶名或密碼”這類消息)。選擇權在你,可以根據應用需要做出選擇。
Subject 登錄成功后,他們就被認為是已認證的,通常你會允許他們使用你的應用。但是僅僅證明了一個用戶的身份并不意味著他們可以對你的應用為所欲為。這就引出了另一個問題,“我如何控制用戶能做或不能做哪些事情?”,決定用戶允許做哪些事情的過程被稱為授權。下面我們將談談 Shiro如何進行授權。
### Authorization授權
授權實質上就是訪問控制——控制用戶能夠訪問應用中的哪些內容,比如資源、Web 頁面等等。多數用戶執行訪問控制是通過使用諸如角色和權限這類概念完成的。也就是說,通常用戶允許或不允許做的事情是根據分配給他們的角色或權限決定的。那么,通過檢查這些角色和權限,你的應用程序就可以控制哪些功能是可以暴露的。如你期望的,Subject API 讓你可以很容易的執行角色和權限檢查。如清單7中的代碼片段所示:如何檢查 Subject被分配了某個角色:
Listing 7. Role Check 角色檢查
```
if ( subject.hasRole(“administrator”) ) {
//顯示‘Create User’按鈕
} else {
//按鈕置灰?
}
```
如你所見,你的應用程序可基于訪問控制檢查打開或關閉某些功能。
權限檢查是執行授權的另一種方法。上例中的角色檢查有個很大的缺陷:你無法在運行時增刪角色。角色名字在這里是硬編碼,所以,如果你修改了角色名字或配置,你的代碼就會亂套!如果你需要在運行時改變角色含義,或想要增刪角色,你必須另辟蹊徑。
為此,Shiro 支持了權限(permissions)概念。權限是功能的原始表述,如‘開門’,‘創建一個博文’,‘刪除‘jsmith’用戶’等。通過讓權限反映應用的原始功能,在改變應用功能時,你只需要改變權限檢查。進而,你可以在運行時按需將權限分配給角色或用戶。
如清單8中,我們重寫了之前的用戶檢查,取而代之使用權限檢查。
Listing 8. Permission Check 權限檢查
```
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 的高級會話管理功能。
### Session Management會話管理
在安全框架領域,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的兩個方法。
Listing 10. Subject’s Session 會話
```
Session session = subject.getSession();
Session session = subject.getSession(boolean create);
```
如你所見,這些方法在概念上等同于 HttpServletRequest API。第一個方法會返回 Subject 的現有會話,或者如果還沒有會話,它會創建一個新的并將之返回。第二個方法接受一個布爾參數,這個參數用于判定會話不存在時是否創建新會話。一旦獲得 Shiro 的會話,你幾乎可以像使用HttpSession 一樣使用它。Shiro 團隊覺得對于 Java 開發者,HttpSession API用起來太舒服了,所以我們保留了它的很多感覺。當然,最大的不同在于,你可以在任何應用中使用 Shiro 會話,不僅限于Web 應用。清單11中顯示了這種相似性。
Listing 11. Session methods 會話的方法
```
Session session = subject.getSession();
session.getAttribute("key", someValue);
Date start = session.getStartTimestamp();
Date timestamp = session.getLastAccessTime();
session.setTimeout(millis); ...
```
### Cryptography 加密
加密是隱藏或混淆數據以避免被偷窺的過程。在加密方面,Shiro的目標是簡化并讓JDK的加密支持可用。
清楚一點很重要,一般情況下,加密不是特定于Subject的,所以它是Shiro API的一部分,但并不特定于Subject。你可以在任何地方使用Shiro的加密支持,甚至在不使用Subject的情況下。對于加密支持,Shiro真正關注的兩個領域是加密哈希(又名消息摘要)和加密密碼。下面我們來看看這兩個方面的詳細描述。
#### Hashing哈希
如果你曾使用過 JDK 的 [MessageDigest](http://download.oracle.com/javase/6/docs/api/java/security/MessageDigest.html) 類,你會立刻意識到它的使用有點麻煩。MessageDigest 類有一個笨拙的基于工廠的靜態方法 API,它不是面向對象的,并且你被迫去捕獲那些永遠都不必捕獲的 Checked Exceptions。如果需要輸出十六進制編碼或 Base64 編碼的消息摘要,你只有靠自己 - 對上述兩種編碼,沒有標準的 JDK 支持它們。Shiro 用一種干凈而直觀的哈希 API 解決了上述問題。
打個比方,考慮比較常見的情況,使用 MD5 哈希一個文件,并確定該哈希的十六進制值。被稱為‘校驗和’,這在提供文件下載時常用到 - 用戶可以對下載文件執行自己的MD5哈希。如果它們匹配,用戶完全可以認定文件在傳輸過程中沒有被篡改。
不使用 Shiro,你需要如下步驟才能完成上述內容:
1. 將文件轉換成字節數組。JDK 中沒有干這事的,故而你需要創建一個輔助方法用于打開 FileInputStream,使用字節緩存區,并拋出相關的IOExceptions,等等。
2. 使用 MessageDigest 類對字節數組進行哈希,處理相關異常,如清單12所示。
3. 將哈希后的字節數組編碼成十六進制字符。JDK 中還是沒有干這事的,你依舊需要創建另外一個輔助方法,有可能在你的實現中會使用位操作和位移動。
Listing 12. JDK’s MessageDigest 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 對哈希和編碼簡化了不少,挽救了你處理在這類問題上所消耗的腦細胞。
#### Ciphers密碼
加密是使用密鑰對數據進行可逆轉換的加密算法。我們使用其保證數據的安全,尤其是傳輸或存儲數據時,以及在數據容易被窺探的時候。
如果你曾經用過 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 加密:
Listing 13. Apache Shiro’s Encryption API 加密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 Support Web支持
最后,但并非不重要,我們將簡單介紹一下 Shiro 的 Web 支持。Shiro附帶了一個幫助保護 Web 應用的強建的 Web 支持模塊。對于 Web 應用,安裝 Shiro 很簡單。唯一需要做的就是在 web.xml 中定義一個 Shiro Servlet 過濾器。清單14中,列出了代碼。
Listing 14. ShiroFilter in web.xml
```
<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-Specific Filter Chains URL特定的Filter鏈
Shiro 通過其創新的URL過濾器鏈功能支持安全特定的過濾規則。它允許你為任何匹配的URL模式指定非正式的過濾器鏈。這意味著, 使用 Shiro 的過濾器機制,你可以很靈活的強制安全規則(或者規則的組合) - 其程度遠遠超過你單獨在web.xml中定義過濾器時所獲得的。清單15中顯示了Shiro INI中的配置片段。
Listing 15. Path-specific Filter Chains 路徑特定的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 Tag Library JSP標簽庫
Shiro 還提供了 JSP 標簽庫,允許你根據當前 Subject 的狀態控制 JSP 頁面的輸出。一個有用的常見示例是在用戶登錄后顯示“Hello "文本。但若是匿名用戶,你可能想要顯示其他內容,如換而顯示“Hello! Register Today!”。清單16顯示了如何使用Shiro的JSP標簽實現這個示例:
Listing 16. JSP Taglib Example 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](https://github.com/waylau/apache-shiro-1.2.x-reference/blob/master/III.%20Web%20Applications/10.%20Web.md)文檔可以了解更多內容。
### Web Session Management Web會話管理
最后值得一提的是 Shiro 在 Web 環境中對會話的支持。
#### Default Http Sessions 缺省Http會話
對于Web應用,Shiro缺省將使用我們習以為常的 Servlet 容器會話作為其會話基礎設施。即,當你調用 subject.getSession()和subject.getSession(boolean) 方法時,Shiro 會返回 Servlet 容器的 HttpSession 實例支持的 Session 實例。這種方式的曼妙之處在于調用 subject.getSession() 的業務層代碼會跟一個 Shiro Session 實例交互 - 還沒有“認識”到它正跟一個基于 Web 的HttpSession 打交道。這在維護架構層之間的清晰隔離時,是一件非常好的事情。
#### Shiro’s Native Sessions in the Web Tier 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 的‘原生’企業會話管理 - 確實是一個非常方便(且必要)的特性。
## Additional Features 附加特性
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。
## Upcoming Features未來的特性
Apache Shiro社 區每天都在壯大,借此,Shiro 的特性亦是如此。在即將發布的版本中,你可能會看到:
- 更干凈的Web過濾機制,無需子類化就可支持更多的插件式過濾器。
- 更多可插拔的缺省Realm實現,優先采用組合而非繼承。你可以插入查找認證和授權數據的組件,無需實現為 Shiro Realm的 子類;
- 強健的 [OpenID](http://openid.net/) 和 [OAuth](http://oauth.net/)(可能是混合)客戶端支持;
- 支持Captcha;
- 針對純無狀態應用的配置簡化(如,許多REST環境);
- 通過請求/響應協議進行多階段認證;
- 通過AuthorizationRequest進行粗粒度的授權;
- 針對安全斷言查詢的[ANTLR](http://www.antlr.org/)語法(比如,(‘role(admin) && (guest || !group(developer))’)
## Summary 總結
Apache Shiro 是一個功能齊全、健壯、通用的Java安全框架,你可以用其為你的應用護航。通過簡化應用安全的四個領域,即認證、授權、會話管理和加密,在真實應用中,應用安全能更容易被理解和實現。Shiro 的簡單架構和兼容 JavaBean 使其幾乎能夠在任何環境下配置和使用。附加的 Web支持和輔助功能,比如多線程和測試支持,讓這個框架為應用安全提供了“一站式”服務。Apache Shiro 開發團隊將繼續前進,精煉代碼庫和支持社區。隨著持續被開源和商業應用采納,可以預期 Shiro 會繼續發展壯大。
## 資源
- Apache Shiro的[主頁](http://shiro.apache.org/)。
- Shiro的[下載](http://shiro.apache.org/download.html)頁面,包含面向 Maven 和 Ant+Ivy 用戶的額外信息。
- Apache Shiro的[文檔](http://shiro.apache.org/documentation.html)頁面,包含指南和參考手冊。
- Apache Shiro [演講視頻和幻燈](http://shiro.apache.org/2011/01/06/apache-shiro-video-and-slide-from-sf-jug-presentation.html),項目的PMC主席Les Hazlewood提供。
- 其他關于Apache Shiro的[文章和PPT](http://shiro.apache.org/articles.html)Apache Shiro的[郵件列表](http://shiro.apache.org/mailing-lists.html)和[論壇](http://shiro.apache.org/forums.html)。
- [Katasoft](http://www.katasoft.com/) - 提供 Apache Shiro 專業支持和應用安全產品的公司。
- *譯者注:*[Apache Shiro 1.2.x 用戶指南](https://github.com/waylau/apache-shiro-1.2.x-reference) :開源項目,一個官方文檔和其他文章的翻譯集合
*譯者注:*本文參考:<http://www.infoq.com/articles/apache-shiro>。如果對本中文翻譯有疑議的或發現勘誤歡迎指正,[點此](https://github.com/waylau/apache-shiro-1.2.x-reference/issues)提問。
- Introduction
- 1. Introduction 介紹
- 2. Tutorial 教程
- 3. Architecture 架構
- 4. Configuration 配置
- 5. Authentication 認證
- 6. Authorization 授權
- 6.1. Permissions 權限
- 7. Realms
- 8. Session Management
- 9. Cryptography 密碼
- 10. Web
- 10.1. Configuration 配置
- 10.2. 基于路徑的 url 安全
- 10.3. Default Filters 默認過濾器
- 10.4. Session Management
- 10.5. JSP Tag Library
- 11. Caching 緩存
- 12. Concurrency & Multithreading 并發與多線程
- 13. Testing 測試
- 14. Custom Subjects 自定義 Subject
- 15. Spring Framework
- 16. Guice
- 17. CAS
- 18. Command Line Hasher
- 19. Terminology 術語
- 20. 10 Minute Tutorial 十分鐘教程
- 21. Beginner's Webapp Tutorial 初學者web應用教程
- 22. Application Security With Apache Shiro 用Shiro保護你的應用安全
- 23. CacheManager 緩存管理
- 24. Apache Shiro Cryptography Features 加密功能