最近因爲項目需要,研究了一下Apache Shiro安全認證框架,把心得記錄下來。
(原創by:西風吹雨)
Apache Shrio是一個安全認證框架,和Spring Security相比,在於他使用了和比較簡潔易懂的認證和授權方式。其提供的native-session(即把用戶認證後的授權信息保存在其自身提供Session中)機制,這樣就可以和HttpSession、EJB Session Bean的基於容器的Session脫耦,到到和客戶端應用、Flex應用、遠程方法調用等都可以使用它來配置權限認證。
1、sessionMode
在普通的WEB項目中,我們可以選擇使用native session或者是HttpSession,通過設置securityManager的sessionMode參數爲http或native即可。
2、realm
我們可以基於jdbc,ldap,text,activeDirectory,jndi等多種方式來獲取用戶基本信息,角色信息,權限信息等。只需要在 securityManager中指定使用相應的realm實現即可,其在這各方面都提供了對應的缺省實現,比如我們常用的基於數據庫表的形式來配置用戶 權限信息,就可以使用其缺省實現的jdbcRealm(org.apache.shiro.realm.jdbc.JdbcRealm)。當然,如果認證 信息來自於多方面,多個不同的來源(比如來自兩個庫中,或者一個數據庫,一個是ldap,再配上一個缺省的基於文本的測試用等等),我們可以爲 securityManager指定realms參數,即把這一組安全配置都配置上。各個具體的realm實現提供了方法來獲取用戶基本信息、角色、權限 等。
realm的授權信息可以存放在Cache中,Cache的名稱可以通過設置其authorizationCacheName參數指定。
3、緩存
目前Shrio缺省提供了基於ehCache來緩存用戶認證信息和授權信息的實現。只需要配置
org.apache.shiro.web.mgt.DefaultWebSecurityManager 這個 cacheManager並設置給SecurityManager即可。如果項目中已經存在使用的ehCacheManager配置(org.springframework.cache.ehcache.EhCacheManagerFactoryBean),DefaultWebSecurityManager則可以指定使用現有的ehCacheManager,如果不指定,它將自行使用缺省配置創建一個。同時,也可以設置cacheManagerConfigFile參數來指定ehCache的配置文件。
下例中的shiro.authorizationCache是用來存放授權信息的Cache,我們在配置realm(如myRealm或 jdbcReaml)時,把authorizationCacheName屬性設置shiro.authorizationCache來對應。
ehcache.xml
<ehcache>
<diskStore path="java.io.tmpdir/tuan-oauth"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!-- We want eternal="true" (with no timeToIdle or
timeToLive settings) because Shiro manages session
expirations explicitly. If we set it to false and
then set corresponding timeToIdle and timeToLive properties,
ehcache would evict sessions without Shiro's knowledge, which would
cause many problems
(e.g. "My Shiro session timeout is 30 minutes - why isn't a session
available after 2 minutes?"
Answer - ehcache expired it due to the timeToIdle property set to
120 seconds.)
diskPersistent=true since we want an enterprise session
management feature - ability to use sessions after
even after a JVM restart.
-->
<cache name="shiro-activeSessionCache"
maxElementsInMemory="10000"
eternal="true"
overflowToDisk="true"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="600"/>
<cache name="shiro.authorizationCache"
maxElementsInMemory="100"
eternal="false"
timeToLiveSeconds="600"
overflowToDisk="false"/>
</ehcache>
當我們把securityManager的sessionMode參數設置爲native時,那麼shrio就將用戶的基本認證信息保存到缺省名稱爲shiro-activeSessionCache 的Cache中
org.apache.shiro.web.mgt.DefaultWebSecurityManager 在sessionMode參數設置爲native時,缺省使用的是DefaultWebSessionManager來管理Session,該 管理類缺省使用的是使用MemorySessionDAO基於內存來保存和操作用戶基本認證信息。如果系統內的用戶數特別多,我們需要使用 CacheSessionDao來基於Cache進行操作,因此,這裏需要顯示配置一個 sessionManager(org.apache.shiro.web.session.mgt.DefaultWebSessionManager), 並配置該sessionManager的sessionDao爲 CacheSessionDao(org.apache.shiro.session.mgt.eis.CachingSessionDAO,需用其實現 類org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO)。配置CacheSessionDao時,我們可以指定屬性activeSessionsCacheName的名稱來替換掉缺省名 shiro-activeSessionCache。
我們再把該sessionManager配置給DefaultWebSecurityManager就可以了。
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager"
ref="cacheManager"/>
<property name="sessionMode"
value="native"/>
<!-- Single realm app. If you have
multiple realms, use the 'realms' property instead.
-->
<property name="realm"
ref="myRealm"/>
<property name="sessionManager"
ref="sessionManager"/>
</bean>
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO"
ref="sessionDAO"/>
</bean>
<bean id="sessionDAO"
class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
<property name="activeSessionsCacheName"
value="shiro-activeSessionCache"/>
</bean>
從以上我們可以看出
a、我們可以指定sessionManager的sessionDao,在某些情況下,我們也可以通過實現自定義的sessionDao來把用戶認證信息 保存在memcache,mongodb,ldap,database中,達到和其他應用共享用戶認證信息的目的,以此達到SSO的目的(當 然,sessionId得一致,這個屬於我們可以在應用商定怎麼設定一致的sessionId的問題)。
b、cacheManager我們也可以自己實現一個,可以根據應用情況來考慮,比如存放在memcache中之類。
4、配置
Web項目中,普通的web項目可以採用ini文件來對shiro進行配置。基於spring的項目可以採用和Spring集成的方式配置。
基於Spring集成的Web項目的基本配置文件如下:
<?xml version="1.0"
encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans
"
xmlns:context="http://www.springframework.org/schema/context
"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance
"
xmlns:util="http://www.springframework.org/schema/util
"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd
">
<!--
=========================================================
Shiro Core Components - Not Spring Specific
=========================================================
-->
<!-- Shiro's main business-tier object for
web-enabled applications
(use DefaultSecurityManager instead when there is no web
environment)-->
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="cacheManager"
ref="cacheManager"/>
<!-- Single realm app. If you have
multiple realms, use the 'realms' property instead.
-->
<property name="sessionMode"
value="native"/>
<property name="realm"
ref="myRealm"/>
</bean>
<!-- Let's use some enterprise caching support for
better performance. You can replace this with any
enterprise
caching framework implementation that you like (Terracotta+Ehcache,
Coherence, GigaSpaces, etc -->
<bean id="cacheManager"
class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- Set a net.sf.ehcache.CacheManager instance here
if you already have one. If not, a new one
will be creaed with a default
config:
-->
<property name="cacheManager"
ref="ehCacheManager"/>
<!-- If you don't have a pre-built
net.sf.ehcache.CacheManager instance to inject, but you want
a specific Ehcache configuration to be used, specify that
here. If you don't, a default
will be used.:
<property name="cacheManagerConfigFile"
value="classpath:some/path/to/ehcache.xml"/>
-->
</bean>
<!-- Used by the SecurityManager to access security
data (users, roles, etc).
Many other realm implementations can be used too
(PropertiesRealm,
LdapRealm, etc. -->
<bean id="jdbcRealm"
class="org.apache.shiro.realm.jdbc.JdbcRealm">
<property name="name"
value="jdbcRealm"/>
<property name="dataSource"
ref="dataSource"/>
<property
name="credentialsMatcher">
<!-- The 'bootstrapDataPopulator' Sha256 hashes the
password
(using the username as the salt) then base64 encodes it:
-->
<bean
class="org.apache.shiro.authc.credential.Sha256CredentialsMatcher">
<!-- true means hex encoded, false means base64
encoded -->
<property name="storedCredentialsHexEncoded"
value="false"/>
<!-- We salt the password using the username, the
most common practice: -->
<property name="hashSalted"
value="true"/>
</bean>
</property>
<property name="authorizationCacheName"
value="shiro.authorizationCache"/>
</bean>
<bean id="myRealm"
class="org.apache.shiro.realm.text.IniRealm"
init-method="init">
<property name="name"
value="myRealm"/>
<property name="authorizationCacheName"
value="shiro.authorizationCache"/>
<property name="resourcePath"
value="classpath:config/myRealm.ini"/>
</bean>
<!--
=========================================================
Shiro Spring-specific integration
=========================================================
-->
<!-- Post processor that automatically invokes
init() and destroy() methods
for Spring-configured Shiro objects so you don't have to
1) specify an init-method and destroy-method attributes for every
bean
definition and
2) even know which Shiro objects require these methods to be
called. -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- Enable Shiro Annotations for Spring-configured
beans. Only run after
the lifecycleBeanProcessor has run: -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager"
ref="securityManager"/>
</bean>
<!-- Secure Spring remoting:
Ensure any Spring Remoting method invocations can be
associated
with a Subject for security checks. -->
<bean id="secureRemoteInvocationExecutor"
class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<property name="securityManager"
ref="securityManager"/>
</bean>
<!-- Define the Shiro Filter here (as a FactoryBean)
instead of directly in web.xml -
web.xml uses the DelegatingFilterProxy to access this
bean. This allows us
to wire things with more control as well utilize nice Spring things
such as
PropertiesPlaceholderConfigurer and abstract beans or anything else
we might need: -->
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager"
ref="securityManager"/>
<property name="loginUrl"
value="/login"/>
<property name="successUrl"
value="/index"/>
<property name="unauthorizedUrl"
value="/unauthorized"/>
<!-- The 'filters' property is not necessary since
any declared javax.servlet.Filter bean
defined will be automatically acquired and available via its
beanName in chain
definitions, but you can perform overrides or parent/child
consolidated configuration
here if you like: -->
<!-- <property
name="filters">
<util:map>
<entry key="aName"
value-ref="someFilterPojo"/>
</util:map>
</property> -->
<property
name="filterChainDefinitions">
<value>
/login = authc
/account = user
/manage = user,roles[admin]
</value>
</property>
</bean>
</beans>
5、基於url資源的權限管理
我們可以簡單配置在shiroFilter的filterChainDefinitions中,也可以考慮通過一個文本文件,我們讀入內容後設置進去。或者通過Ini類來裝入Ini文件內容,到時取出urls的部分來設置給shiroFilter的filterChainDefinitions。也可以把這部分數據存入數據庫表中,到時讀出一個Map來設置給shiroFilter的filterChainDefinitionsMap屬性。
6、url的配置
authc是認證用戶(rememberMe的用戶也必須再次登錄才能訪問該url),配置成user才能讓rememberMe用戶也可以訪問。
7、rememberMe Cookie的處理
Shiro有一套缺省機制,由CookieRememberMeManager實現。
其有一個SimpleCookie類,保存對應的用戶信息等。每次保存時,系統把SimpleCookie的信息設置好之後,先用DefaultSerializer把其用jvm缺省序列化方式序列化成byte[],然後再用cipherService(缺省是aes加密算法)來加密該byte[],最後用Base64.encodeToString(serialized)壓縮成一個字符串,再寫入名稱爲rememberMe的Cookie中。
讀取時,通過把該rememberMe Cookie的內容用byte[] decoded = Base64.decode(base64)解壓出該byte[],再用cipherService解密,最後用DefaultSerializer反序列化出來該SimpleCookie類。
如果我們有自定義的rememberMe Cookie需要處理,特別是在和其他網站一起SSO,通過訪問主域的Cookie來獲取記錄的用戶信息時,我們需要重新實現rememberMeManager(可以考慮繼承AbstractRememberMeManager), 和根據實際用的序列化方式Serializer來實現一個(比如考慮通用性,用json方式序列化)。在Spring配置中,配置好 RememberMeManager,裝配上sericerlizer和cipherService(根據實際情況選用適當的加密算法),最後把 rememberMeManager設置給DefaultWebSecurityManager即可。如果非常簡單的cookie,可以直接實現RememberMeManager的幾個接口方法也行。
說到這裏, Apache Shiro的一些基本配置和擴展情況應該差不多了。關於它的授權和使用等模型,其官網有基本的說明,也有相關的例子。這裏主要是針對其實際應用中的一些使用和擴展方式。
原文鏈接:http://blog.sina.com.cn/s/blog_6638b10d0100pd88.html