Apache Shiro系列之五,概述 —— 配置


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]
    這個Section的描述將放在   Web   章節。
 
原文地址:http://shiro.apache.org/configuration.html

 

 本系列相關:

Apache Shiro系列四概述 ——Shiro的架構




免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM