安全认证框架-Apache Shiro研究心得

XHuiLin 2011-03-15

最近因为项目需要,研究了一下Apache Shiro安全认证框架,把心得记录下来。

(by 西风吹雨 原创)

http://blog.sina.com.cn/s/blog_6638b10d0100pd88.html

     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

expirationsexplicitly.IfwesetittofalseandthensetcorrespondingtimeToIdleandtimeToLiveproperties,

ehcachewouldevictsessionswithoutShiro'sknowledge,whichwouldcausemanyproblems

(e.g."MyShirosessiontimeoutis30minutes-whyisn'tasessionavailableafter2minutes?"

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

evenafteraJVMrestart.-->

<cachename="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">

<propertyname="cacheManager"ref="cacheManager"/>

<propertyname="sessionMode"value="native"/>

<!--Singlerealmapp.Ifyouhavemultiplerealms,usethe'realms'propertyinstead.-->

<propertyname="realm"ref="myRealm"/>

<propertyname="sessionManager"ref="sessionManager"/>

    </bean>

    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">

<propertyname="sessionDAO"ref="sessionDAO"/>

    </bean>

    <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">

<propertyname="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"?>

<beansxmlns="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/beanshttp://www.springframework.org/schema/beans/spring-beans-3.0.xsd

http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-3.0.xsd

       http://www.springframework.org/schema/utilhttp://www.springframework.org/schema/util/spring-util-3.0.xsd">

    <!-- =========================================================

ShiroCoreComponents-NotSpringSpecific

         ========================================================= -->

    <!-- Shiro's main business-tier object for web-enabled applications

(useDefaultSecurityManagerinsteadwhenthereisnowebenvironment)-->

<beanid="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

<propertyname="cacheManager"ref="cacheManager"/>

<!--Singlerealmapp.Ifyouhavemultiplerealms,usethe'realms'propertyinstead.-->

<propertyname="sessionMode"value="native"/>

<propertyname="realm"ref="myRealm"/>

    </bean>

    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise

cachingframeworkimplementationthatyoulike(Terracotta+Ehcache,Coherence,GigaSpaces,etc-->

<beanid="cacheManager"class="org.apache.shiro.cache.ehcache.EhCacheManager">

<!--Setanet.sf.ehcache.CacheManagerinstancehereifyoualreadyhaveone.Ifnot,anewone

willbecreaedwithadefaultconfig:-->

<propertyname="cacheManager"ref="ehCacheManager"/>

<!--Ifyoudon'thaveapre-builtnet.sf.ehcache.CacheManagerinstancetoinject,butyouwant

aspecificEhcacheconfigurationtobeused,specifythathere.Ifyoudon't,adefault

willbeused.:

<propertyname="cacheManagerConfigFile"value="classpath:some/path/to/ehcache.xml"/>-->

    </bean>

    <!-- Used by the SecurityManager to access security data (users, roles, etc).

Manyotherrealmimplementationscanbeusedtoo(PropertiesRealm,

LdapRealm,etc.-->

<beanid="jdbcRealm"class="org.apache.shiro.realm.jdbc.JdbcRealm">

<propertyname="name"value="jdbcRealm"/>

<propertyname="dataSource"ref="dataSource"/>

<propertyname="credentialsMatcher">

<!--The'bootstrapDataPopulator'Sha256hashesthepassword

(usingtheusernameasthesalt)thenbase64encodesit:-->

<beanclass="org.apache.shiro.authc.credential.Sha256CredentialsMatcher">

<!--truemeanshexencoded,falsemeansbase64encoded-->

<propertyname="storedCredentialsHexEncoded"value="false"/>

<!--Wesaltthepasswordusingtheusername,themostcommonpractice:-->

<propertyname="hashSalted"value="true"/>

</bean>

</property>

<propertyname="authorizationCacheName"value="shiro.authorizationCache"/>

    </bean>

    <bean id="myRealm" class="org.apache.shiro.realm.text.IniRealm" init-method="init">

<propertyname="name"value="myRealm"/>

<propertyname="authorizationCacheName"value="shiro.authorizationCache"/>

        <property name="resourcePath" value="classpath:config/myRealm.ini"/>

    </bean>

    <!-- =========================================================

ShiroSpring-specificintegration

=========================================================-->

<!--Postprocessorthatautomaticallyinvokesinit()anddestroy()methods

forSpring-configuredShiroobjectssoyoudon'thaveto

1)specifyaninit-methodanddestroy-methodattributesforeverybean

definitionand

2)evenknowwhichShiroobjectsrequirethesemethodstobe

called.-->

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after

thelifecycleBeanProcessorhasrun:-->

<beanclass="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

depends-on="lifecycleBeanPostProcessor"/>

<beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

<propertyname="securityManager"ref="securityManager"/>

    </bean>

<!--SecureSpringremoting:EnsureanySpringRemotingmethodinvocationscanbeassociated

withaSubjectforsecuritychecks.-->

<beanid="secureRemoteInvocationExecutor"class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">

<propertyname="securityManager"ref="securityManager"/>

    </bean>

    <!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -

web.xmlusestheDelegatingFilterProxytoaccessthisbean.Thisallowsus

towirethingswithmorecontrolaswellutilizeniceSpringthingssuchas

PropertiesPlaceholderConfigurerandabstractbeansoranythingelsewemightneed:-->

<beanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

<propertyname="securityManager"ref="securityManager"/>

<propertyname="loginUrl"value="/login"/>

<propertyname="successUrl"value="/index"/>

<propertyname="unauthorizedUrl"value="/unauthorized"/>

<!--The'filters'propertyisnotnecessarysinceanydeclaredjavax.servlet.Filterbean

definedwillbeautomaticallyacquiredandavailableviaitsbeanNameinchain

definitions,butyoucanperformoverridesorparent/childconsolidatedconfiguration

hereifyoulike:-->

<!--<propertyname="filters">

<util:map>

<entrykey="aName"value-ref="someFilterPojo"/>

</util:map>

</property>-->

<propertyname="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的一些基本配置和扩展情况应该差不多了。关于它的授权和使用等模型,其官网有基本的说明,也有相关的例子。这里主要是针对其实际应用中的一些使用和扩展方式。

相关推荐