讓Apache Shiro保護你的應用[轉]

嘗試保護你的應用時,你是否有過挫敗感?是否以爲現有的Java安全解決方案難以使用,只會讓你更糊塗?本文介紹的Apache Shiro,是一個不一樣尋常的Java安全框架,爲保護應用提供了簡單而強大的方法。本文還解釋了Apache Shiro的項目目標、架構理念以及如何使用Shiro爲應用安全保駕護航。html

什麼是Apache Shiro?

Apache Shiro(發音爲「shee-roh」,日語「堡壘(Castle)」的意思)是一個強大易用的Java安全框架,提供了認證、受權、加密和會話管理功能,可爲任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。java

Shiro爲解決下列問題(我喜歡稱它們爲應用安全的四要素)提供了保護應用的API:web

  • 認證 - 用戶身份識別,常被稱爲用戶「登陸」;
  • 受權 - 訪問控制;
  • 密碼加密 - 保護或隱藏數據防止被偷窺;
  • 會話管理 - 每用戶相關的時間敏感的狀態。

Shiro還支持一些輔助特性,如Web應用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素。算法

爲什麼要建立Apache Shiro?

對於一個框架來說,使其有存在價值的最好例證就是有讓你去用它的緣由,它應該能完成一些別人沒法作到的事情。要理解這一點,須要瞭解Shiro的歷史以及建立它時的其餘替代方法。數據庫

在2008年加入Apache軟件基金會以前,Shiro已經5歲了,以前它被稱爲JSecurity項目,始於2003年初。當時,對於Java應用開發人員而言,沒有太多的通用安全替代方案 - 咱們被Java認證/受權服務(或稱爲JAAS)牢牢套牢了。JAAS有太多的缺點 - 儘管它的認證功能尚可忍受,但受權方面卻顯得拙劣,用起來使人沮喪。此外,JAAS跟虛擬機層面的安全問題關係很是緊密,如判斷JVM中是否容許裝入一個類。做爲應用開發者,我更關心應用最終用戶能作什麼,而不是個人代碼在JVM中能作什麼。apache

因爲當時正從事應用開發,我也須要一個乾淨、容器無關的會話機制。在當時,「這場遊戲」中惟一可用的會話是HttpSessions,它須要Web容器;或是EJB 2.1裏的有狀態會話Bean,這又要EJB容器。而我想要的一個與容器脫鉤、可用於任何我選擇的環境中的會話。編程

最後就是加密問題。有時,咱們須要保證數據安全,可是Java密碼架構(Java Cryptography Architecture)讓人難以理解,除非你是密碼學專家。API裏處處都是Checked Exception,用起來很麻煩。我須要一個乾淨、開箱即用的解決方案,能夠在須要時方便地對數據加密/解密。設計模式

因而,縱觀2003年初的安全情況,你會很快意識到尚未一個大一統的框架知足全部上述需求。有鑑於此,JSecurity(即以後的Apache Shiro)誕生了。api

今天,你爲什麼願意使用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)的地方。在示例中,咱們看到配置了兩個對象:

  1. cm對象,是Shiro的HashedCredentialsMatcher類實例。如你所見,cm實例的各屬性是經過「嵌套點」語法進行配置的 - 在清單3中能夠看到IniSecurityManagerFactory使用的慣例,這種方法表明了對象圖導航和屬性設置。
  2. 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的示例中,咱們看到有三步:

  1. 裝入用來配置SecurityManager及其構成組件的INI配置文件;
  2. 根據配置建立SecurityManager實例(使用Shiro的工廠概念,它表述了工廠方法設計模式);
  3. 使應用可訪問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環境,下面讓咱們來討論,做爲一名開發者該如何使用這個框架。

認證

認證是覈實用戶身份的過程。也就是說,當用戶使用應用進行認證時,他們就在證實他們就是本身所說的那我的。有時這也理解爲「登陸」。它是一個典型的三步驟過程。

  1. 收集用戶的身份信息,稱爲當事人(principal),以及身份的支持證實,稱爲證書(Credential)
  2. 將當事人和證書提交給系統。
  3. 若是提交的證書與系統指望的該用戶身份(當事人)匹配,該用戶就被認爲是通過認證的,反之則被認爲未經認證的。

這個過程的常見例子是你們都熟悉的「用戶/密碼」組合。多數用戶在登陸軟件系統時,一般提供本身的用戶名(當事人)和支持他們的密碼(證書)。若是存儲在系統中的密碼(或密碼錶示)與用戶提供的匹配,他們就被認爲經過認證。

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,你須要以下步驟才能完成上述內容:

  1. 將文件轉換成字節數組。JDK中沒有幹這事的,故而你須要建立一個輔助方法用於打開FileInputStream,使用字節緩存區,並拋出相關的IOExceptions,等等。
  2. 使用MessageDigest類對字節數組進行哈希,處理相關異常,如清單12所示。
  3. 將哈希後的字節數組編碼成十六進制字符。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的子類;
  • 強健的OpenIDOAuth(多是混合)客戶端支持;
  • 支持Captcha;
  • 針對純無狀態應用的配置簡化(如,許多REST環境);
  • 經過請求/響應協議進行多階段認證;
  • 經過AuthorizationRequest進行粗粒度的受權;
  • 針對安全斷言查詢的ANTLR語法(好比,(‘role(admin) && (guest || !group(developer))’)

總結

Apache Shiro是一個功能齊全、健壯、通用的Java安全框架,你能夠用其爲你的應用護航。經過簡化應用安全的四個領域,即認證、受權、會話管理和加密,在真實應用中,應用安全能更容易被理解和實現。Shiro的簡單架構和兼容JavaBean使其幾乎可以在任何環境下配置和使用。附加的Web支持和輔助功能,好比多線程和測試支持,讓這個框架爲應用安全提供了「一站式」服務。Apache Shiro開發團隊將繼續前進,精煉代碼庫和支持社區。隨着持續被開源和商業應用採納,能夠預期Shiro會繼續發展壯大。

資源

關於做者

Les Hazlewood是Apache Shiro PMC主席以及Katasoft的CTO和合夥創辦人,該公司是專一於應用安全產品以及提供Apache Shiro專業支持的創業公司。做爲專業Java開發人員及企業架構師,以及以前Bloomberg,Delta Airlines和JBoss的高級角色,Les擁有10年的的經驗。Les積極從事開源開發已有9年時間,提交或作出貢獻的項目有Spring框架、Hibernate、JBoss,OpenSpaces,固然還有JSecurity,Apache Shiro的前身。Les目前居住在加州的聖馬特奧,不編程時,他會練習劍道和學習日語。

查看英文原文: Application Security With Apache Shiro

相關文章
相關標籤/搜索