cuiyi's blog(崔毅 crazycy)

          記錄點(diǎn)滴 鑒往事之得失 以資于發(fā)展
          數(shù)據(jù)加載中……

          SpringMVC+MyBatis - 5 Security-Shiro-01

          文章摘錄處

          安全認(rèn)證框架-APACHE SHIRO研究心得

          最近因?yàn)轫?xiàng)目需要,研究了一下Apache Shiro安全認(rèn)證框架,把心得記錄下來。(原創(chuàng)by:西風(fēng)吹雨) 

          Apache Shrio是一個安全認(rèn)證框架,和Spring Security相比,在于他使用了和比較簡潔易懂的認(rèn)證和授權(quán)方式。其提供的native-session(即把用戶認(rèn)證后的授權(quán)信息保存在其自身提供Session中)機(jī)制,這樣就可以和HttpSession、EJB Session Bean的基于容器的Session脫耦,到到和客戶端應(yīng)用、Flex應(yīng)用、遠(yuǎn)程方法調(diào)用等都可以使用它來配置權(quán)限認(rèn)證。 

          1、sessionMode 
          在普通的WEB項(xiàng)目中,我們可以選擇使用native session或者是HttpSession,通過設(shè)置securityManager的sessionMode參數(shù)為http或native即可。 

          2、realm
          我們可以基于jdbc,ldap,text,activeDirectory,jndi等多種方式來獲取用戶基本信息,角色信息,權(quán)限信息等。只需要在securityManager中指定使用相應(yīng)的realm實(shí)現(xiàn)即可,其在這各方面都提供了對應(yīng)的缺省實(shí)現(xiàn),比如我們常用的基于數(shù)據(jù)庫表的形式來配置用戶權(quán)限信息,就可以使用其缺省實(shí)現(xiàn)的jdbcRealm(org.apache.shiro.realm.jdbc.JdbcRealm)。

          當(dāng)然,如果認(rèn)證信息來自于多方面,多個不同的來源(比如來自兩個庫中,或者一個數(shù)據(jù)庫,一個是ldap,再配上一個缺省的基于文本的測試用等等),我們可以為securityManager指定realms參數(shù),即把這一組安全配置都配置上。各個具體的realm實(shí)現(xiàn)提供了方法來獲取用戶基本信息、角色、權(quán)限等。 realm的授權(quán)信息可以存放在Cache中,Cache的名稱可以通過設(shè)置其authorizationCacheName參數(shù)指定。 

          3、緩存 
          目前Shrio缺省提供了基于ehCache來緩存用戶認(rèn)證信息和授權(quán)信息的實(shí)現(xiàn)。只需要配置 org.apache.shiro.web.mgt.DefaultWebSecurityManager 這個 cacheManager并設(shè)置給SecurityManager即可。

          如果項(xiàng)目中已經(jīng)存在使用的ehCacheManager配置(org.springframework.cache.ehcache.EhCacheManagerFactoryBean),DefaultWebSecurityManager則可以指定使用現(xiàn)有的ehCacheManager,如果不指定,它將自行使用缺省配置創(chuàng)建一個。

          同時,也可以設(shè)置cacheManagerConfigFile參數(shù)來指定ehCache的配置文件。 下例中的shiro.authorizationCache是用來存放授權(quán)信息的Cache,我們在配置realm(如myRealm或jdbcReaml)時,把a(bǔ)uthorizationCacheName屬性設(shè)置shiro.authorizationCache來對應(yīng)。 

          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>

          當(dāng)我們把securityManager的sessionMode參數(shù)設(shè)置為native時,那么shrio就將用戶的基本認(rèn)證信息保存到缺省名稱為shiro-activeSessionCache 的Cache中 

          org.apache.shiro.web.mgt.DefaultWebSecurityManager 在sessionMode參數(shù)設(shè)置為native時,缺省使用的是DefaultWebSessionManager來管理Session,該管理類缺省使用的是使用MemorySessionDAO基于內(nèi)存來保存和操作用戶基本認(rèn)證信息。

          如果系統(tǒng)內(nèi)的用戶數(shù)特別多,我們需要使用CacheSessionDao來基于Cache進(jìn)行操作,因此,這里需要顯示配置一個sessionManager(org.apache.shiro.web.session.mgt.DefaultWebSessionManager),并配置該sessionManager的sessionDao為CacheSessionDao(org.apache.shiro.session.mgt.eis.CachingSessionDAO,需用其實(shí)現(xiàn)類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,在某些情況下,我們也可以通過實(shí)現(xiàn)自定義的sessionDao來把用戶認(rèn)證信息保存在memcache,mongodb,ldap,database中,達(dá)到和其他應(yīng)用共享用戶認(rèn)證信息的目的,以此達(dá)到SSO的目的(當(dāng)然,sessionId得一致,這個屬于我們可以在應(yīng)用商定怎么設(shè)定一致的sessionId的問題)。 

          b、cacheManager我們也可以自己實(shí)現(xiàn)一個,可以根據(jù)應(yīng)用情況來考慮,比如存放在memcache中之類。 

          4、配置 
          Web項(xiàng)目中,普通的web項(xiàng)目可以采用ini文件來對shiro進(jìn)行配置?;趕pring的項(xiàng)目可以采用和Spring集成的方式配置。 基于Spring集成的Web項(xiàng)目的基本配置文件如下: 
           
          <?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資源的權(quán)限管理 
          我們可以簡單配置在shiroFilter的filterChainDefinitions中,也可以考慮通過一個文本文件,我們讀入內(nèi)容后設(shè)置進(jìn)去?;蛘咄ㄟ^Ini類來裝入Ini文件內(nèi)容,到時取出urls的部分來設(shè)置給shiroFilter的filterChainDefinitions。也可以把這部分?jǐn)?shù)據(jù)存入數(shù)據(jù)庫表中,到時讀出一個Map來設(shè)置給shiroFilter的filterChainDefinitionsMap屬性。 

          6、url的配置 
          authc是認(rèn)證用戶(rememberMe的用戶也必須再次登錄才能訪問該url),配置成user才能讓rememberMe用戶也可以訪問。 

          7、rememberMe Cookie的處理 
          Shiro有一套缺省機(jī)制,由CookieRememberMeManager實(shí)現(xiàn)。其有一個SimpleCookie類,保存對應(yīng)的用戶信息等。

          每次保存時,系統(tǒng)把SimpleCookie的信息設(shè)置好之后,先用DefaultSerializer把其用jvm缺省序列化方式序列化成byte[],然后再用cipherService(缺省是aes加密算法)來加密該byte[],最后用Base64.encodeToString(serialized)壓縮成一個字符串,再寫入名稱為rememberMe的Cookie中。 

          讀取時,通過把該rememberMe Cookie的內(nèi)容用byte[] decoded = Base64.decode(base64)解壓出該byte[],再用cipherService解密,最后用DefaultSerializer反序列化出來該SimpleCookie類。 

          如果我們有自定義的rememberMe Cookie需要處理,特別是在和其他網(wǎng)站一起SSO,通過訪問主域的Cookie來獲取記錄的用戶信息時,我們需要重新實(shí)現(xiàn)rememberMeManager(可以考慮繼承AbstractRememberMeManager),和根據(jù)實(shí)際用的序列化方式Serializer來實(shí)現(xiàn)一個(比如考慮通用性,用json方式序列化)。

          在Spring配置中,配置好RememberMeManager,裝配上sericerlizer和cipherService(根據(jù)實(shí)際情況選用適當(dāng)?shù)募用芩惴ǎ詈蟀裷ememberMeManager設(shè)置給DefaultWebSecurityManager即可。如果非常簡單的cookie,可以直接實(shí)現(xiàn)RememberMeManager的幾個接口方法也行


          【Apache Shiro in Spring】自定義filterChainDefinitions和Realm
          文章出處

          在Spring Context中定義shiroFilter(org.apache.shiro.spring.web.ShiroFilterFactoryBean)時需要為其filterChainDefinitions property賦值,這個屬性是個chainName-to-chainDefinition map of chain definitions,用于為URL定義過濾策略。

          filterChainDefinitions是一個set method,他調(diào)用setFilterChainDefinitionMap(section),F(xiàn)ilterChainDefinitionMap是個Field。

          比如,這是我定義的:

          /404.htm = anon /main!main.html = anon /**.html = perms[myPerm_1]


          在這里引用一下某前輩總結(jié)的chainDefinition,如下:

          rest:比如/admins/user/**=rest[user],根據(jù)請求的方法,相當(dāng)于/admins/user/**=perms[user:method] ,其中method為post,get,delete等。

          port:比如/admins/user/**=port[8081],當(dāng)請求的url的端口不是8081是跳轉(zhuǎn)到schemal://serverName:8081?queryString,其中schmal是協(xié)議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString是你訪問的url里的?后面的參數(shù)。

          perms:比如/admins/user/**=perms[user:add:*],perms參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,比如/admins/user/**=perms["user:add:*,user:modify:*"],當(dāng)有多個參數(shù)時必須每個參數(shù)都通過才通過,想當(dāng)于isPermitedAll()方法。

          roles:比如/admins/user/**=roles[admin],參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,當(dāng)有多個參數(shù)時,比如/admins/user/**=roles["admin,guest"],每個參數(shù)通過才算通過,相當(dāng)于hasAllRoles()方法。

          anon:比如/admins/**=anon 沒有參數(shù),表示可以匿名使用。

          authc:比如/admins/user/**=authc表示需要認(rèn)證才能使用,沒有參數(shù)

          authcBasic:比如/admins/user/**=authcBasic沒有參數(shù)表示httpBasic認(rèn)證

          ssl:比如/admins/user/**=ssl沒有參數(shù),表示安全的url請求,協(xié)議為https

          user:比如/admins/user/**=user沒有參數(shù)表示必須存在用戶,當(dāng)?shù)侨氩僮鲿r不做檢查


          一般情況下,我們可以將模塊作為一個授權(quán)單位,例如:

          /blog!**.html = user

          偶爾也會有將每一個URL作為一個授權(quán)單位進(jìn)行控制,例如:

          /blog!doAddNewArticle.html = authc,perms[addArticle]

          但是URL的數(shù)量讓人頭疼,也許可以每開發(fā)一個功能的時候打開XML文件寫入U(xiǎn)RL然后同步到SVN,或者我們也可以找一個人專門做這些事情,無論是添加、刪除功能還是修改方法名都通知這個人去做...換位思考一下,我不希望我是這個人...

          所以我要把這些URL放在數(shù)據(jù)庫進(jìn)行管理。我要把他們統(tǒng)統(tǒng)Query出來放到filterChainDefinitions里。
          我需要裝配一個Bean,實(shí)際上他是一個org.springframework.beans.factory.FactoryBean<Section>的實(shí)現(xiàn)。

          <bean id="chainFilterBuff"   class="king.common.ChainFilterBuff">
              <property name="filterChainDefinitions">
              /404.htm = anon
              /main!main.html = anon
              </property>
          </bean>

          然后將其注入:

          <bean id="shiroFilter1" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
              <property name="securityManager" ref="securityManager" />
              <property name="loginUrl" value="/main!main.html" />
              <property name="successUrl" value="/main!main.html" />
              <property name="unauthorizedUrl" value="/unAuthorized.htm" />
              <property name="filterChainDefinitionMap" ref="chainFilterBuff" />
          </bean>


          Java代碼中implements FactoryBean<Ini.Section>并實(shí)現(xiàn)需要Override的method,關(guān)鍵是getObject這個method:

          private String filterChainDefinitions;
          @Override
          public Section getObject() throws Exception {
              Ini ini = new Ini();
              ini.load(filterChainDefinitions);   //先載入XML中定義好的chain
              Ini.Section section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
              /*
               *  省略讀取步驟
               *  繼續(xù)加入數(shù)據(jù)庫中的chain
               *  section.put("/**", "authc, roles[admin]");
               
          */
                                                                                                                                                                                                                                                                                                                                                 
              return section;
          }

          等等,這些僅僅是定義了權(quán)限。

          我們需要在用戶訪問這些URL的時候去驗(yàn)證一下用戶是否具備當(dāng)前URL權(quán)限。
          所以我定義了(事實(shí)上我們必須定義一個Realm,ShiroFilterFactoryBean需要SecurityManager,而我們使用的SecurityManager的實(shí)現(xiàn)類DefaultWebSecurityManager則需要一個Realm!):

          <bean id="shiroDataBaseRealm" class="king.security.KingMainRealm">

          并且Override了接口定義的method:

          public class KingMainRealm extends AuthorizingRealm {
              @Override
              protected AuthorizationInfo doGetAuthorizationInfo( //授權(quán)
                      PrincipalCollection principals) {
                  UserBean _user = (UserBean) principals.fromRealm(getName()).iterator().next();
                  SimpleAuthorizationInfo authInfo = new SimpleAuthorizationInfo();
                          /*
                           * 省略
                           
          */
                  return authInfo;
              }
              @Override
              protected AuthenticationInfo doGetAuthenticationInfo( //認(rèn)證
                      AuthenticationToken token) throws AuthenticationException {
                  UserBean user = new UserBean();
                  UsernamePasswordToken userToken = (UsernamePasswordToken)token;
                  user.setUserName(userToken.getUsername());
                  user.setPassword(userToken.getPassword());
                                                            
                  return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
              }
          }

          若已定義了需要請求的URL,用戶登錄時doGetAuthorizationInfo會被調(diào)用,剩下的就是為每一個用戶管理這些權(quán)限了。

          也許我們可以創(chuàng)建角色關(guān)聯(lián)多個權(quán)限,用戶關(guān)聯(lián)多個角色,類似這樣的設(shè)置不同的層次。
          按你喜歡的方式去做吧 

          posted on 2014-07-11 09:38 crazycy 閱讀(2267) 評論(0)  編輯  收藏 所屬分類: JavaEE技術(shù)

          主站蜘蛛池模板: 子长县| 京山县| 永济市| 奉贤区| 英德市| 阿勒泰市| 梁平县| 桦甸市| 三河市| 江陵县| 兴文县| 奇台县| 本溪| 扶余县| 吉林市| 时尚| 贵定县| 浦县| 长泰县| 泰和县| 宁安市| 岢岚县| 洛浦县| 溆浦县| 鹤岗市| 调兵山市| 马关县| 营口市| 贵阳市| 镇平县| 永修县| 中西区| 沛县| 宜州市| 右玉县| 韩城市| 彰化市| 海兴县| 新绛县| 杭锦旗| 平和县|