Apache Shiro 配置
Apache Shiro 配置
Shiro 可以在任何環境下工作,從簡單的命令行程序到大型企業級集群項目,因為環境的多樣化,可以通過許多途徑來配合當前環境的配置方式進行配置,在本章我們來了解一下shiro核心支持的配置方式。
多樣的配置選項:
Shiro的SecurityManager和其它支持組件都和JavaBean兼容,所以Shiro幾乎可以用任何方式進行配置,比如Java、XML(Spring, JBoss, Guice, 等等),YAML, JSON,Groovy Builder markup等。
在程序中配置
創建一個SecurityManager并使之可用最簡單的方法就是創建一個org.apache.shiro.mgt.DefaultSecurityManager對象并寫將它寫入代碼,例如:
Realm realm = //instantiate or acquire a Realm instance. We'll discuss Realms later.
SecurityManager securityManager = new DefaultSecurityManager(realm);
//Make the SecurityManager instance available to the entire application via static memory:
SecurityUtils.setSecurityManager(securityManager);
僅僅三行代碼,你就可以擁有一個適用于任何程序的功能全面的shiro環境,多么簡單。
SecurityMangger Object Graph
如同我們在架構(Architecture)中討論過的,Shiro SecurityMangger本質上是一個由一套安全組件組成的對象模塊視圖(graph),因為與JavaBean兼容,所以可以對所有這些組件調用的getter和 setter 方法來配置SecurityManager和它的內部對象視圖。
例如,你想用一個自定義的SessionDAO來定制Session Management從而配置一個SecurityManager實例,你就可以使用SessionManager的setSessionDAO方法直接set這個SessionDAO。
...
DefaultSecurityManager securityManager = new DefaultSecurityManager(realm);
SessionDAO sessionDAO = new CustomSessionDAO();
((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO);
...
使用這些函數,你可以配置SecurityManager視圖(graph)中的任何一部分。
雖然在程序中配置很簡單,但它并不是我們現實中配置的完美解決方案。在幾種情況下這種方法可能并不適合你的程序:
它需要你確切知道并實例化一個直接實現(direct implementation),然而更好的做法是你并不需要知道這些實現也不需要知道從哪里找到它們。
因為JAVA類型安全的特性,你必須對通過get*獲取的對象進行強制類型轉換,這么多強制轉換非常的丑陋、累贅并且會和你的類緊耦合。
SecurityUtils.setSecurityManager方法會將SecurityManager實例化為虛擬機的單獨靜態實例,在大多數程序中沒有問題,但如果有多個使用shiro的程序在同一個JVM中運行時,各程序有自己獨立的實例會更好些,而不是共同引用一塊靜態內存。
改變配置就需要重新編譯你的程序。
然而,盡管有這些不足,在程序中定制的這種方法在限制內存(memory-constrained)的環境中還是很有價值的,像智能電話程序。如果你的程序不是運行在一個限制內存的環境中,你會發現基于文本的配置會更易讀易用。
INI 配置
大多數程序已經改為使用基于文本的配置,不需要依靠代碼就可進行修改,對于不熟悉shiro API的人來說,也易于理解。
為了確保具有共性的基于文本配置的途徑適用于任何環境而且減少對第三方的依賴,shiro支持使用INI創建SecurityManager對象視圖(graph)以及它支持的組件,INI易讀易配置,很容易創建并且對大多數程序都很適合。
通過INI資源創建SecurityManager
這里舉兩個通過INI配置創建SecurityManager的例子。
從INI資源創建SecurityManager
我們可以從一個INI資源路徑創建一個SecurityManager實例,資源可以通過文件系統(前綴為file:)、類路徑(classpath:)或者URL(url:)獲得,下面的例子使用一個Factory從類路徑根目錄加載shiro.ini并返回一個SecurityManager實例。
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.IniSecurityManagerFactory;
...
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
通過INI實例創建SecurityManager
INI配置可以通過org.apache.shiro.config.Ini類用程序方式創建,這個INI類類似于JDK的java.util.Properties類,但支持通過section名分割。例子如下:
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniSecurityManagerFactory;
...
Ini ini = new Ini();
//populate the Ini instance as necessary
...
Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini);
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
現在我們知道如何使用INI配置文件創建一個SecurityManager,讓我們仔細了解一下如何定義一個shiro INI配置文件。
INI Sections
INI 基于文本配置,在獨立命名的區域內通過成對的鍵名/鍵值組成。鍵名在每個區域內必須唯一,但在整個配置文件中并不需要這樣(這點和JDK的Properties不同),每一個區域(section)可以看作是一個獨立的Properties定義。
注釋行可以用“#”或“;”標識。
這里是一個shiro可以理解的各section的示例。
# =======================
# Shiro INI configuration
# =======================
[main]
# Objects and their properties are defined here,
# Such as the securityManager, Realms and anything
# else needed to build the SecurityManager
[users]
# The 'users' section is for simple deployments
# when you only need a small number of statically-defined
# set of User accounts.
[roles]
# The 'roles' section is for simple deployments
# when you only need a small number of statically-defined
# roles.
[urls]
# The 'urls' section is used for url-based security
# in web applications. We'll discuss this section in the
# Web documentation
[main]
[main]區域是配置程序SecurityManager實例及其支撐組件的地方,如Realm。
通過INI配置像SecurityManager的對象實例及其支撐組件聽起來是一件很困難的事情,因為在這里我們只能用鍵名/鍵值對。但通過定義一些對象視圖(graphs)可以理解的慣例,你發現你完全可以這樣做。shiro利用這些假定的慣例來實現一個簡單而簡明的配置途徑。
我們經常將這種方法認為是“可憐人的(poor man's)”的依賴注入,雖然不及成熟的Spring/Guice/JBoss的XML文件強大,但你會發現它可以做很多事情而且并不復雜,當然當那配置途徑也可以使用,但對shiro來講并不是必須的。
僅僅吊一下胃口,這里是一個簡單的可以使用的[main]配置,下面我們會詳細介紹,但你可能發現你僅憑直覺就可以理解一些。
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
myRealm = com.company.security.shiro.DatabaseRealm
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
myRealm.password = secret
myRealm.credentialsMatcher = $sha256Matcher
securityManager.sessionManager.globalSessionTimeout = 1800000
定義一個對象
在[main]中包含以下片段。
[main]
myRealm = com.company.shiro.realm.MyRealm
...
這一行實例化了一個類型為com.company.shiro.realm.MyRealm 的對象實例并且使對象使用myRealm作為名稱以便于將來引用和配置。
如果對象實例化時實現了 org.apache.shiro.util.Nameable 接口,Nameable.setName方法將被以該名(在此例中為myRealm)命名的對象調用。
設置對象屬性
原始值
簡單的原始值屬性可以使用下面的等于符號進行設置:
...
myRealm.connectionTimeout = 30000
myRealm.username = jsmith
...
這些配置行轉換為方法調用就是:
...
myRealm.setConnectionTimeout(30000);
myRealm.setUsername("jsmith");
...
怎么做到的呢?它假定所有對象都是兼容JavaBean的POJO。在設置這些屬性時,Shiro默認使用Apache通用的BeanUtils來完成這項復雜的工作,所以雖然INI值是文本,BeanUtils知道如何將這些字符串值轉換為適合的原始值類型并調用合適的JavaBeans的setter方法。
引用值
如果你想設置的值并不是一個原始值,而是另一個對象怎么辦呢?你可以使用一個$符來引用一個之前定義的實例,如:
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
myRealm.credentialsMatcher = $sha256Matcher
...
這定義了名為sha256Matcher的對象并且使用BeanUtils將其設置到myRealm的實例中(通過調用 myRealm.setCredentialsMatcher(sha256Matcher)方法)。
嵌套屬性
通過在等號左側使用點符號,你可以得到你希望設置對象視圖最終的對象/屬性,例如下面這行配置:
...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
轉換邏輯為(通過BeanUtils):
securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
用這種方法訪問的層數需要多深可以有多深:
object.property1.property2....propertyN.value = blah
BeanUtils 屬性支持
BeanUtils支持任何指定的屬性操作,在shiro[main]區域中setProperty方法將被調用,包括集合(set)/列表(list)/圖(map),查看Apache Commons BeanUtils Website和文檔了解更多的信息。
字節數組值
因為原始的字節數組不能直接在文本中定義,我們必須使用字節數組的文本編碼。可以使用64位編碼(默認)或者16位編碼,默認為64位編碼因為使用64位編碼實際文字會少一些--它擁有很大的編碼表,這意味著你的標識會更短(對于文本配置來講會好一些)。
# 'cipherKey'屬性是一個字節數組,默認的,字節數組屬性被編碼為64位的文本值
securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
如果你想使用16位編碼,你必須在字串前面加上0x前綴:
securityManager.rememberMeManager.cipherKey= 0x3707344A4093822299F31D008
集合屬性
列表(Lists)、集合(Sets)、圖(Maps)可以像其它屬性一樣設置--直接設置或者像嵌套屬性一樣,對于列表和集合,只需指定一個逗號分割的值集或者對象引用集。
如定義一些SessionListeners:
sessionListener1 = com.company.my.SessionListenerImplementation
...
sessionListener2 = com.company.my.other.SessionListenerImplementation
...
securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
對于圖(Maps),你可以指定以逗號分割的鍵-值對列表,每個鍵-值之間用冒號分割:
object1 = com.company.some.Class
object2 = com.company.another.Class
...
anObject = some.class.with.a.Map.property
anObject.mapProperty = key1:$object1, key2:$object2
在上面的例子中,$object1 引用的對象將存于鍵key1之下,也就是map.get("key1")將返回object1。你也可以使用其它對象作為鍵值:
anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
...
注意事項
順序問題
上述INI格式和約定非常方便也非常易懂,但它并沒有另外一種text/XML的配置路徑強大,通過上述途徑進行配置需要知道非常重要的一件事情就是順序問題!
小心
每一個對象實例以及每一個指定的值都將按照其在[main]區域中產生的順序的執行,這些行最終轉換為JavaBeans的getter/setter方法調用,這些方法按同樣的順序調用。
當你寫配置文件的時候要牢記于此。
覆蓋實例
每一個對象都可以被后定義的新實例覆蓋,例如,第二個myRealm定義將重寫第一個:
...
myRealm = com.company.security.MyRealm
...
myRealm = com.company.security.DatabaseRealm
...
這樣的結果是myRealm是com.company.security.DatabaseRealm實例而前面的實例不會被使用(會作為垃圾回收)。
默認Default SecurityManager
你可能注意到在以上所有例子中都沒有定義SecurityManager,而我們直接設置其嵌套屬性:
myRealm = ...
securityManager.sessionManager.globalSessionTimeout = 1800000
...
這是因為securityManager實例是特殊的--它已經為你實例化過了并且準備好了,所以你并不需要知道指定的實例化SecurityManager的實現類。
當然,如果你確實想指定你自己的實現類,你可以像上面的覆蓋實例那樣定義你自己的實現:
...
securityManager = com.company.security.shiro.MyCustomSecurityManager
...
當然,很少需要這樣--shiro的securityManager實現可以按需求進行定制,你可能要問一下自己(或者用戶群)你是否真的需要這樣做。
[users]
[users]區域允許你定義一組靜態的用戶帳號,這對于那些只有少數用戶帳號并且用戶帳號不需要在運行時動態創建的環境來說非常有用。下面是一個例子:
[users]
admin = secret
lonestarr = vespa, goodguy, schwartz
darkhelmet = ludicrousspeed, badguy, schwartz
自動生成IniRealm
定義非空的[users]或[roles]區域將自動創建org.apache.shiro.realm.text.IniRealm實例,在[main]區域下生成一個可用的iniRealm,你可以像上面配置其它對象那樣配置它。
格式
[users]區域下每一行必須和下面的形式一致:
username = password, roleName1, roleName2, ..., roleNameN
等號左邊的值是用戶名;
等號右側第一個值是用戶密碼,密碼是必須的;
密碼之后用逗號分割的值是賦予用戶的角色名,角色名是可選的。
密碼加密
如果你不希望[users]區域下的密碼以明文顯示,你可以用你喜歡的哈希算法(MD5, Sha1, Sha256,等)來加密它們,將加密后的字符串作為密碼值,默認的,密碼建議用16位編碼算法,但也可以用64位編碼算法替代(如下)。
簡單的安全密碼
為了節約時間獲得最佳實踐,你可以使用shiro的Command Line Hasher,它可以加密密碼和其它類型的資源,尤其使給INI[user]密碼加密變得非常簡單。
一旦你指定了加密后的密碼值,你必須告訴shiro它們是加密的,你可以通過配置配置在[main]隱含創建的iniRealm相應的CredentialsMatcher實現來告知你使用的哈希算法:
[main]
...
sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
...
iniRealm.credentialsMatcher = $sha256Matcher
...
[users]
# user1 = sha256-hashed-hex-encoded password, role1, role2, ...
user1 = 2bb80d537b1da3e38bd30361aa855686bde0eacd7162fef6a25fe97bf527a25b, role1, role2, ...
你可以像配置其他對象那樣配置CredentialsMatcher的所有屬性,例如,指定使用salting或者有多少hash iterations執行,可以查看org.apache.shiro.authc.credential.HashedCredentialsMatcher Java文檔更好地理解hashing策略,可能會很有幫助。
例如,如果你用64位編碼方式取代了16位編碼方式,你應該指定:
[main]
...
# true = hex, false = base64:
sha256Matcher.storedCredentialsHexEncoded = false
[roles]
[roles]區域允許你將權限和在[users]定義的角色對應起來,同樣的,這對于那些只有少數用戶帳號并且用戶帳號不需要在運行時動態創建的環境來說非常有用。下面是一個例子:
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
格式
[roles]區域下的每一行必須用下面的格式定義角色-權限的鍵/值對應關系。
rolename = permissionDefinition1, permissionDefinition2, ..., permissionDefinitionN
權限定義可以是非常隨意的字符串,但大部分用戶還是希望使用易用而靈活的和 org.apache.shiro.authz.permission.WildcardPermission形式一致的字符串格式。查看Permissions文檔獲取更多關于權限的信息和你可以如何利用它為你服務。
內部用法
注意如果一個特定的權限定義需要用到逗號分隔(如:printer:5thFloor:print,info),你需要將該定義用雙引號括起來從而避免出錯:"printer:5thFloor:print,info"。
沒有權限的角色
如果你有不需要權限的角色,不需要將它們列入[roles]區域,僅僅在 [users]區域定義角色名就可以創建它們(如果它們尚不存在)。
[urls]
該區域選項將在web章節討論。
為文檔加把手
我們希望這篇文檔可以幫助你使用Apache Shiro進行工作,社區一直在不斷地完善和擴展文檔,如果你希望幫助shiro項目,請在你認為需要的地方考慮更正、擴展或添加文檔,你提供的任何點滴幫助都將擴充社區并且提升Shiro。
提供你的文檔的最簡單的途徑是將它發送到用戶論壇(http://shiro-user.582556.n2.nabble.com/)或郵件列表(http://shiro.apache.org/mailing-lists.html)
原文地址:http://shiro.apache.org/configuration.html