Shiro設計的初衷就是可以運行於任何環境:無論是簡單的命令行應用程序還是復雜的企業集群應用。由於運行環境的多樣性,所以有多種配置機制可用於配置,本節我們將介紹Shiro內核支持的這幾種配置機制。
小貼士:
多種配置方案:
Shiro的SecurityManager是和JavaBean兼容的,所以我們可以使用諸如Java、Xml(Spring、Jboss、Guice等)、YAML、Json、Groovy等配置方式。
一、基於Java代碼的配置
最簡單的創建並且使用SecurityManager的方式就是直接在代碼中創建org.apache.shiro.mgt.DefaultSecurityManager類實例,比如:
1 Realm realm =//instantiate or acquire a Realm instance. We'll discuss Realms later. 2 SecurityManager securityManager =newDefaultSecurityManager(realm); 3 //Make the SecurityManager instance available to the entire application via static memory: 4 SecurityUtils.setSecurityManager(securityManager);
只需區區三行代碼,我們就已經為任何類型的應用程序配置好了一個全功能的Shiro運行環境,你看,多簡單。
SecurityManager對象圖譜:
就像我們在架構一節中介紹的,SecurityManager的實現是模塊化的,而且可以兼容JavaBean,所以你可以通過setter和getter方法來配置SecurityManager及其內部組件。
比如如果你想把一個自定義的SessionDAO配置為SecurityManager的Session管理器,你可以直接調用SessionManager的setSessionDAO方法。
... DefaultSecurityManager securityManager =newDefaultSecurityManager(realm); SessionDAO sessionDAO =newCustomSessionDAO(); ((DefaultSessionManager)securityManager.getSessionManager()).setSessionDAO(sessionDAO); ...
你可以通過這種調用setter方法的方式來設置SecurityManager的任何內置組件。但是對於現實的應用程序來說,這不是一種理想的配置方式。主要有以下幾點原因:
#,這種直接編碼的方式要求我們知道這個具體的實現類在哪,並且要自己去創建他。而我們一般建議是依賴於抽象而不是具體,所以最好不要讓我知道他具體的實現在哪里。
#,由於java的類型安全特性,當我們通過getter方法獲取到某個類的具體實現之后,我們將不得不把他們強制類型轉換為具體的類型,如此多的強制類型轉換太丑了,不是一種好的編程實踐。
#,如果我們通過SecurityUtils.setSecurityManager方法為當前的應用設置一個虛擬機范圍內的靜態SecurityManager對象,在大多數應用中都是ok的。但是如果我們要在一個虛擬機上運行多個使用Shiro的應用程序時,就可能會出亂子了。所以如果能夠為每個應用程序創建一個的單例就更好了;
#,每次你要修改一下Shiro的配置都不得不重新編譯程序;
雖然有以上提到的種種缺點,但是如果你要在一個內存受限的環境(比如智能手機)中使用Shiro,使用基於java代碼的配置還是不錯的選擇。而如果內存不太受限的話,使用推薦使用基於文本的配置,因為他對用戶更友好,具有更好的可讀性。
二、INI配置
為了讓這個文本配置方案能夠在所有的開發環境中使用,並且盡可能的減少對於第三方工具的依賴,我們選擇了INI格式來配置SecurityManager及其相關組件。INI具有易讀、易配置的特性,可以適用於絕大多數的應用。
(一)從INI文件中創建一個SecurityManager
一下將提供兩種基於INI配置文件創建SecurityManager的方法。
從
INI資源文件中創建SecurityManager
我們可以通過一個INI資源的路徑來創建一個SecurityManager,資源可以通過文件系統、classpath、或者url中獲取,不同的獲取方式需要在資源路徑前加不同的前綴,分別是file:, classpath 或者 url:,下面這個例子我們使用一個工廠類從根classpath中找到shiro.ini文件,然后實例化了一個SecurityManager對象。
1 import org.apache.shiro.SecurityUtils; 2 import org.apache.shiro.util.Factory; 3 import org.apache.shiro.mgt.SecurityManager; 4 import org.apache.shiro.config.IniSecurityManagerFactory; 5 ... 6 Factory<SecurityManager> factory =newIniSecurityManagerFactory("classpath:shiro.ini"); 7 SecurityManager securityManager = factory.getInstance(); 8 SecurityUtils.setSecurityManager(securityManager);
從INI類實例中創建SecurityManager
如果有需要,我們也可以通過org.apache.shiro.config.Ini類來做INI配置,這個Ini類的API和java.util.Properties類比較像,只是在接口中需要傳入Section的名稱。
例如:
1 import org.apache.shiro.SecurityUtils; 2 import org.apache.shiro.util.Factory; 3 import org.apache.shiro.mgt.SecurityManager; 4 import org.apache.shiro.config.Ini; 5 import org.apache.shiro.config.IniSecurityManagerFactory; 6 ... 7 Ini ini =newIni(); 8 //populate the Ini instance as necessary 9 ... 10 Factory<SecurityManager> factory =newIniSecurityManagerFactory(ini); 11 SecurityManager securityManager = factory.getInstance(); 12 SecurityUtils.setSecurityManager(securityManager);
以上我們已經知道如何通過INI配置來實例化SecurityManager對象,下面就讓我們來看看一個真實的Shiro的配置文件到底長什么樣。
(二)
INI Secions
所謂的INI文件其實就是按Section分隔的鍵值對的集合,不同的Section名稱不同,每個Section內的鍵名稱要有唯一性。每個Section可以看做就是一個Properties。
注釋行可以井號(#) 開頭,也可以 分號(;)開頭。、
下面就是一個Shiro能夠解析的INI文件的例子,他的這些Section名稱是Shiro所支持的。
#======================= #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
(1) [main]
[main]段主要用於配置SecurityManager及其他的依賴項,比如Realms。
要使用INI這種簡單鍵值對文件格式來配置SecurityManager對象及其依賴項這種層級關系感覺難度有點大,只要稍微加上一些約定,你會發現INI文件能做的遠比我們想象的要多,我們把這種基於INI的配置叫做“窮人”的依賴注入。雖然沒有Spring/JBoss之類的高富帥那么強大,但是已經足夠滿足Shiro的配置要求了。下面是一個[main]段的配置例子,我們會在下文詳細解釋,不過我估計在解釋之前,你也能猜個八九不離十了。
[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接口,則程序會使用參數"myRealm"來調用Nameable.setName接口。
設置對象屬性
基本類型
基本類型屬性可以像下面這樣直接賦值。
... myRealm.connectionTimeout =30000 myRealm.username = jsmith ...
這段配置翻譯成Java代碼后是這樣的:
1 ... 2 myRealm.setConnectionTimeout(30000); 3 myRealm.setUsername("jsmith"); 4 ...
這是如何做到的呢? 這里嘉定所有的對象都是和Java Bean兼容的POJO對象。
在這種約定的前提下,當給對象設置屬性時,Shiro會將所有的臟活、累活都交給Apache Common BeanUtils來干,雖然我們在INI文件內配置的是文本,但是BeanUtils知道如何將一個字符串的值轉換為基本類型,並且調用該對象的對應的setter方法來給該POJO設置屬性。
引用類型
如果要設置是引用類型怎么辦?你可以用一個美元符號來引用前文中定義的對象,像下面這樣,就這么簡單。
1 ... 2 sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher 3 ... 4 myRealm.credentialsMatcher = $sha256Matcher 5 ...
嵌套屬性
你可以像下面這樣引用嵌套屬性,給屬性賦值,不管有多少個層級,都可以這么用。
... securityManager.sessionManager.globalSessionTimeout =1800000 ...
BeanUtils會把他翻譯成下面這樣的代碼:
1 securityManager.getSessionManager().setGlobalSessionTimeout(1800000);
這種嵌套的層級可以要多深有多深,比如:
object.property1.property2.....propertyN.value=blah
小貼士:BeanUtil支持的屬性設置
只要是BeanUtil支持的屬性設置方式你都可以在Shiro配置文件的[main] Section中配置。包括對於set/list/map屬性的設置。詳情可以參考 Apache Commons BeanUtils Website官方文檔。
字節數組
因為在文本文件中無法直接表示二進制樹,所以必須使用一種可以使用文本編碼的方式來表示二進制數組,有兩種選擇,一種是:BASE64,一種是十六進制字符串。默認使用BASE64編碼,因為表示相同長度的二進制,BASE64需要的字節更少。這顯然更合適文本配置。
#The'cipherKey' attribute is a byte array.Bydefault, text values #for all byte array properties are expected to be Base64 encoded: securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA== ...
十六進制的文本當然也是可以的,你要記得在文本的前面加上0x。
securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
集合屬性
我們可以像設置其他屬性一樣去設置list、set、map類型的屬性,不管是直接的屬性還是嵌套屬性。對於list和set,我們可以直接使用逗號分隔的值(或者引用)來設置,例如:
sessionListener1 = com.company.my.SessionListenerImplementation ... sessionListener2 = com.company.my.other.SessionListenerImplementation ... securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
對於map,你可以指定一系列都好分隔的鍵值對。鍵值對內部使用 冒號來作為鍵和值的分隔符,例如:
object1 = com.company.some.Class object2 = com.company.another.Class ... anObject = some.class.with.a.Map.property anObject.mapProperty = key1:$object1, key2:$object2
你還可以直接使用對象來作為key,如下:
anObject.map = $objectKey1:$objectValue1, $objectKey2:$objectValue2
一些思考
順序問題
在INI文件中配置的順序決定了他們翻譯成Java代碼之后的順序,這塊要小心。
重寫實例
后定義的同名對象會覆蓋之前定義的對象,如下第二個myRealm會覆蓋掉第一個myRealm的定義,所以最終myRealm是com.company.security.DatabaseRealm的對象實例,而前一個定義的realm將永遠不會被引用了。(自然也就被垃圾回收了)。
... myRealm = com.company.security.MyRealm ... myRealm = com.company.security.DatabaseRealm ...
默認的SecurityManager
你可能已經注意到,在上文的配置中,我們並沒有創建SecurityManager對象就直接去設置他的嵌套屬性了。
myRealm =... securityManager.sessionManager.globalSessionTimeout =1800000 ...
SecurityManager是特例,系統已經自動為你創建好了一個SecurityManager對象,你就只管用就好了。當然,如果你說我一定要使用字節實現的一個SecurityManager,那也不是不行,直接像下面這么寫就可以了,就像 重寫實例 章節中說的,這樣就會覆蓋掉系統自動創建的SecurityManager對象了。
... securityManager = com.company.security.shiro.MyCustomSecurityManager ...
當然,這種情況很少發生。因為Shiro默認提供的SecurityManager具有很好的定制性,你幾乎可以為配置任何屬性。所以如果你發現自己要寫一個自定義的SecurityManager的時候,不妨先問問自己:這真的有必要嗎?
(2) [users]
[users]Setion允許你定義一組用戶賬號。在那些只需要很少的用戶賬號,或者是那些不需要在運行時動態創建用戶賬號的運行環境下,這個功能很實用。你可以像下面這么寫。
[users] admin = secret lonestarr = vespa, goodguy, schwartz darkhelmet = ludicrousspeed, badguy, schwartz
小貼士:自動生成的IniRealm
一旦Shiro發現INI文件的[users], [roles]章節不為空,他就會自動創建一個org.apache.shiro.realm.text.IniRealm類的實例並命名為iniRealm,所以你可以在[main]章節中像配置其他對象一樣給iniRealm配置屬性。
行格式
在[users]的每一行將定義成如下格式
username=password, roleName1, roleName2, ..., roleNameN
#,左邊的key是用戶名;
#,右邊是逗號分隔的密碼和角色,其中第一個而是密碼,后續的是角色名,角色可以有多個;
#,角色是可選的;
密碼加密
如果你不想讓密碼直接用明文顯示,也可以使用任何你喜歡的加密算法(MD5,Sha1,Sha256, 等等)來對密碼做加密,然后把加密之后的文本復制到INI文件中。加密之后的密碼默認應該以十六進制的文本,當然也可以是BASE64的,具體看下問的說明。
小貼士:簡單安全的密碼
對密碼做加密的最佳實踐是使用Shiro提供的 Command Line Hasher工具,他會對密碼或者其他你想要加密的文本做哈希,這個工具對於要放在Shiro的INI配置文件的[users]中顯示的密碼最為實用。
如果你對密碼做了加密,那么你就要告訴Shiro你對密碼做了加密,不然他就不認識了。你可以在[main]端中配置Shiro隱式生成的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,...
和其他屬性一樣,你可以給CredentialMatcher配置任何值來體現你的哈希策略,比如指定一個鹽值,或者是哈希迭代的次數。要想更好的了解哈希策略,你可以查看
org.apache.shiro.authc.credential.HashedCredentialsMatcher
的API文檔。
比如,如果用戶的密碼是BASE64編碼,而不是默認的16進制,則應該像下面這么配置。
[main] ... #true= hex,false= base64: sha256Matcher.storedCredentialsHexEncoded =false
(3)[roles]
這個session用於定義角色與權限的對照關系,同樣的,這種配置方式對於那些只有少數幾種角色,並且不需要在運行時動態創建角色的應用程序特別實用。
[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
此處
permissionDefinition 可以是任意文本,不過一般我們會建議使用和
org.apache.shiro.authz.permission.WildcardPermission
格式兼容的文本格式,這種格式簡單而又靈活。可以查看權限(
Permissions)章節來了解更多關於這種權限格式的信息。
小貼士:權限內部的分號
如果在權限內部要有分號,則在角色定義中必須要用雙引號將他們括起來,以避免解析錯誤,比如printer:5thFloor:print,info,就要寫成“printer:5thFloor:print,info”
小貼士:不需要權限的角色定義
如果你的角色是不需要關聯權限的,那么你不需要在[roles]章節中來列出他們,你盡管在[users]章節中使用他們就是了。如果系統中不存在這個角色,則Shiro會自動創建對應的角色。
(4)[urls]
原文地址:http://shiro.apache.org/configuration.html
本系列相關: