權限控制方案之——集成shiro

概述:

         上一篇文章中我們介紹的權限控制方案的實現方式是通過URL攔截實現的,這裏介紹通過shiro實現權限控制。shiro是apache下的開源的權限管理框架。

Shiro架構:

        

         Subject:主體,是用戶和程序的統一抽象。

         SecurityManager:安全管理器,認證和授權的核心處理類。

         Authenticator:認證器,主體認證的處理類。

         Authorizer:授權器,主體授權的處理類。

         SessionManager:shiro提供的一套session管理機制。

         SessionDao:對於session的存儲需要用到sessionDao。

         CacheManager;緩存管理器,主要對session和相關數據進行緩存管理。

         Realm:通過realm間接連接數據源,認證和授權需要通過realm存取認證和授權數據。

         Cryptography:密碼管理器,shiro提供的一套加密解密組建。

 

整合shiro

         實現方式:

         1、通過shiro默認的IniRealm實現(開發中一般不用該方式),該方式需要將認證和授權信息配置在一個ini文件中,shiro會默認通過IniRealm讀取該文件信息,實現認證。

         2、通過自定義Realm實現(一般都需要自定義Realm),自定義的Realm可以自定義認證和授權信息獲取的方式(例如從數據庫中查詢),自定義的Realm繼承AuthorizingRealm。

         這裏主要介紹通過自定義Realm實現認證和授權的方式。下面通過將shiro整合到項目中的過程來介紹shiro的基本用法和shiro的工作原理。

         整合過程:

         1、添加jar包

         主要包括:shiro和spring整合的jar包,shiro和web應用整合的jar包和shiro一些基本的包。

         2、配置shiro的filter

         上篇文章我們介紹通過URL攔截中用到了攔截器,shiro內部也是用到了filter攔截進行實現。

         配置信息:

        

<!-- 將shiroFilter作爲spring的Bean,由Spring來管理該Filter,實現和其他Filter的統一 -->
	<filter>
		<filter-name>shiroFilter</filter-name>
		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
		<!-- 設置true表示由servlet容器控制filter的生命週期 -->
		<init-param>
			<param-name>targetFilterLifecycle</param-name>
			<param-value>true</param-value>
		</init-param>
		<!-- 設置spring容器filter指定的Bean的id,如果不設置則找與filter-name一致的bean-->
		<init-param>
			<param-name>targetBeanName</param-name>
			<param-value>shiroFilter</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>shiroFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>


         3、applicationContext-shiro.xml

         在applicationContext-shiro.xml中配置shiroFilter需要的Bean及其他相關配置。

        

<!-- web.xml中shiro的filter對應的bean -->
<!-- Shiro 的Web過濾器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
	<property name="loginUrl" value="/login.action" />
	<!-- 認證成功跳轉到first.action-->
	<property name="successUrl" value="/first.action"/>
	<!-- 指定沒有權限操作時跳轉頁面-->
	<property name="unauthorizedUrl" value="/refuse.jsp" />
	<!-- 自定義filter配置 -->
	<property name="filters">
		<map>
			<!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
			<entry key="authc" value-ref="formAuthenticationFilter" />
		</map>
	</property>
	
	<!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
	<property name="filterChainDefinitions">
		<value>
			<!-- 對靜態資源設置匿名訪問 -->
			/images/** = anon
			/js/** = anon
			/styles/** = anon
			/validatecode.jsp = anon
			
			<!-- 只需請求logout.action地址即可清除session-->
			/logout.action = logout
			<!--授權配置,例queryItems.action請求需要item:query權限-->
			/items/queryItems.action = perms[item:query]
			/items/editItems.action = perms[item:edit]
			<!-- /** = authc 所有url都必須認證通過纔可以訪問-->
			/** = authc
		</value>
	</property>
</bean>

<!-- securityManager屬性配置 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="customRealm" />
	<!-- 注入緩存管理器 -->
	<property name="cacheManager" ref="cacheManager"/>
	<!-- 注入session管理器 -->
	<property name="sessionManager" ref="sessionManager" />
</bean>

<!-- 自定義的realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
	<!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
	<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>

<!-- 憑證匹配器,後邊有介紹 -->
<bean id="credentialsMatcher"
	class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
	<property name="hashAlgorithmName" value="md5" />
	<property name="hashIterations" value="1" />
</bean>

<!-- 緩存管理器,後邊將會介紹關於引入ehcache緩存 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    	<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
</bean>

<!--session管理器,讓shiro對session管理-->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- session的失效時長,單位毫秒 -->
    <property name="globalSessionTimeout" value="600000"/>
    <!-- 刪除失效的session -->
    <property name="deleteInvalidSessions" value="true"/>
</bean>

<!-- 自定義form認證過慮器 -->
<!-- 基於Form表單的身份驗證過濾器,不配置將採用默認值,默認值爲username和password -->
<bean id="formAuthenticationFilter" 
class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
	<!-- 表單中賬號的input名稱 -->
	<property name="usernameParam" value="username" />
	<!-- 表單中密碼的input名稱 -->
	<property name="passwordParam" value="password" />
</bean>


         4、認證:

         實現原理:由上邊的配置可知,當用戶進行認證時,會去請求loginurl進行認證,中間FormAuthenticationFilter進行攔截,取出request中攜帶的username和password參數(注:這裏默認爲username和password,也可以自行配置),FormAuthenticationFilter會調用realm(自定義的嗎?)進行認證,同時傳給realm一個token參數,username和password封裝在token中,realm會根據username查詢用戶信息,如果查詢到則返回,如果查詢不到則會返回null,這時FormAuthenticationFilter會向request中設置異常參數和信息。

        

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		
		//token中封裝了用戶輸入的用戶名和密碼,從token中取出用戶名
		String userCode = (String) token.getPrincipal();

		//根據用戶輸入的userCode從數據庫查詢
		SysUser sysUser = null;
		try {
			sysUser = sysService.findSysUserByUserCode(userCode);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		// 如果查詢不到返回null
		if(sysUser==null){
			//此處返回null,ModularRealmAuthenticator拋出UnknownAccountException(用戶不存在)異常
			return null; 
		}
		//獲取用戶散列密碼
		String password = sysUser.getPassword();
		//獲取鹽
		String salt = sysUser.getSalt();
		
		ActiveUser activeUser = new ActiveUser();
		activeUser.setUserid(sysUser.getId());
		activeUser.setUsercode(sysUser.getUsercode());
		activeUser.setUsername(sysUser.getUsername());
		
		List<SysPermission> menus  = null;
		try {
			//根據用戶id取出菜單
			menus = sysService.findMenuListByUserId(sysUser.getId());
		} catch (Exception e) {
			e.printStackTrace();
		}
		//用戶菜單設置到activeUser
		activeUser.setMenus(menus);
		//將activeUser設置到simpleAuthenticationInfo返回
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				activeUser, password,ByteSource.Util.bytes(salt), this.getName());
		return simpleAuthenticationInfo;
	}


         5、登出

         對於登出可以不用去實現具體的退出操作,只需要訪問一個指定的登出的url,因爲中間會由LogoutFilter攔截,同時清除session數據。配置方式見applicationContext-shiro.xml中的logout配置

         6、授權配置

         說明:對於shiro的授權配置有三種方法:

                  1、通過配置文件進行配置。

                  2、通過註解進行配置。

                  3、通過jsp標籤進行配置。

         對於配置文件的配置方式見applicationContext-shiro.xml中的授權配置部分。

         實現原理爲:在用戶請求到/users/deleteUser.action時會被PermissionsAuthorizationFilter攔截,filter根據配置發現需要“item:query”權限,這時PermissionAuthorizationFilter會調用realm從數據庫中獲取權限信息,如果發現用戶擁有該權限會放行,如果發現沒有會轉到前邊配置中指定的refuse.jsp頁面。

         7、權限判斷

        

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		
		//獲取身份信息
		ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();
		
		List<SysPermission> permissionList = null;
		try {
			//獲取用戶擁有的權限信息
			permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
		} catch (Exception e) {
			e.printStackTrace();
		}
		List<String> permissions = new ArrayList<String>();
		if(permissionList!=null){
			for(SysPermission sysPermission:permissionList){
				permissions.add(sysPermission.getPercode());
			}
		}
		
		//返回授權信息,將查詢到的權限信息設置到simpleAuthorizationInfo中
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		simpleAuthorizationInfo.addStringPermissions(permissions);
		return simpleAuthorizationInfo;
	}


         8、設置憑證匹配器

         由於在數據庫中存放的密碼是經過加密算法的散列值,所以在shiro也需要對取到的密碼進行相同的加密算法才能進行密碼比對,這裏通過在配置文件中配置的憑證匹配器對加密算法進行配置,對於憑證匹配器的配置見applicationContext-shiro.xml。

         上邊對於授權的方式我們使用的是通過配置文件進行實現的,但是這樣的缺點在,URL和對應的權限成對配置,這樣將造成很大的配置量,相對來說比較麻煩;下邊我們說明一下如何通過註解的方式進行配置。

 

整合改進

         註解方式實現授權:

         上邊介紹了對於shiro的授權配置有三種方式,上邊的方式是通過配置文件的方式實現的,下邊介紹一下如何通過註解方式和jsp標籤方式進行授權配置。

         開啓註解:

         由於註解方式內部使用了spring的AOP方式,所以需要首先開啓spring的AOP,同時開啓shiro的註解支持。配置如下:

        

	<!-- 開啓aop,對類代理 -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- 開啓shiro註解支持 -->
	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>


         註解使用:

         註解的使用方式爲,在需要進行權限控制的方法上添加相應註解,註解的寫法爲:

         @RequiresPermissions(“users:delete”),表示執行此方法需要“users:delete”權限。


         Jsp標籤實現授權:

         對於通過jsp標籤實現授權需要引入shiro的自定義標籤

         <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>

         標籤使用:shiro提供的標籤很多,這裏介紹其中幾個:

         1、認證成功後可以顯示的內容放在<shiro:authenticated>內。

         2、當用戶有指定角色時才顯示的內容放在<shiro:hasRole name="角色名">

         3、當用戶有指定的權限時才顯示的內容放在<shiro:hasPermission name="item:query">

         在實際應用當中,通過註解和jsp標籤的方式相對多些。


         引入緩存:

         對於上邊的使用,無論是通過何種方式配置的權限,存在一個較大的問題是,對於權限的判斷,每次都需要realm從數據庫中查詢權限信息進行比對,這樣對系統的性能消耗是很大的,爲了解決該問題,我們爲shiro引入緩存處理,這樣就只需要在用戶第一次訪問時去查詢數據庫,再後邊就可以直接從緩存中取,這裏使用的是ehcache。

         1、添加ehcache的jar包。

         2、配置cacheManager,需要將cacheManager注入到securityManager中,對於相關配置見applicationContext-shiro.xml。

         3、創建shiro-ehcache.xml,定義ehcache的使用方案,具體配置如下:

        

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
	<!--diskStore:緩存數據持久化的目錄地址  -->
	<diskStore path="F:\develop\ehcache" />
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		diskPersistent="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>

         其他

         shiro提供了在用戶退出後緩存的自動清空處理,然而,在用戶登錄期間對用戶的緩存進行更新處理,需要調用realm的clearCached方法對緩存進行清空。

 

總結:

         對於利用shiro對權限的控制和前邊基於URL攔截的控制進行對比來說,shiro的控制功能更加強大一些,但是對於框架的依賴性大些,雖然shiro本身是不依賴與其他框架的,但是這裏和項目整合還是依賴了spring框架,在使用shiro時如果使用註解的方式進行權限控制會更加方便和直觀。這裏主要介紹了shiro和項目整合中的基本用法,其中對於認證和授權的流程在後邊進行介紹,這些只是shiro的一些基本用法,對於shiro的其他應用特點還有待繼續瞭解和學習,也希望這篇文章可以對你有所幫助吧。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章