Shiro【受權過濾器、與ehcache整合、驗證碼、記住我】

前言

本文主要講解的知識點有如下:javascript

  • Shiro受權過濾器使用
  • Shiro緩存
    • 與Ehcache整合
  • Shiro應用->實現驗證碼功能
  • 記住我功能

1、受權過濾器測試

咱們的受權過濾器使用的是permissionsAuthorizationFilter來進行攔截。咱們能夠在application-shiro中配置filter規則html

<!--商品查詢須要商品查詢權限 -->
		/items/queryItems.action = perms[item:query]
		/items/editItems.action = perms[item:edit] 
複製代碼

測試流程: 一、在applicationContext-shiro.xml中配置filter規則java

  • <!--商品查詢須要商品查詢權限 -->
  • /items/queryItems.action = perms[item:query]

二、用戶在認證經過後,請求/items/queryItems.action 三、被PermissionsAuthorizationFilter攔截,發現須要「item:query」權限 四、PermissionsAuthorizationFilter 調用realm中的doGetAuthorizationInfo獲取數據庫中正確的權限 五、PermissionsAuthorizationFilter對item:query 和從realm中獲取權限進行對比,若是「item:query」在realm返回的權限列表中,受權經過。web

realm中獲取認證的信息,查詢出該用戶對應的權限,封裝到simpleAuthorizationInfo中,PermissionsAuthorizationFilter會根據對應的權限來比對。spring

@Override
	protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) {
		
		//從 principals獲取主身份信息
		//將getPrimaryPrincipal方法返回值轉爲真實身份類型(在上邊的doGetAuthenticationInfo認證經過填充到SimpleAuthenticationInfo中身份類型),
		ActiveUser activeUser =  (ActiveUser) principals.getPrimaryPrincipal();
		
		//根據身份信息獲取權限信息
		//從數據庫獲取到權限數據
		List<SysPermission> permissionList = null;
		try {
			permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//單獨定一個集合對象 
		List<String> permissions = new ArrayList<String>();
		if(permissionList!=null){
			for(SysPermission sysPermission:permissionList){
				//將數據庫中的權限標籤 符放入集合
				permissions.add(sysPermission.getPercode());
			}
		}
		
		
	/* List<String> permissions = new ArrayList<String>(); permissions.add("user:create");//用戶的建立 permissions.add("item:query");//商品查詢權限 permissions.add("item:add");//商品添加權限 permissions.add("item:edit");//商品修改權限 */		//....
		
		//查到權限數據,返回受權信息(要包括 上邊的permissions)
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		//將上邊查詢到受權信息填充到simpleAuthorizationInfo對象中
		simpleAuthorizationInfo.addStringPermissions(permissions);

		return simpleAuthorizationInfo;
	}
複製代碼

在bean中咱們已經配置了:若是沒有權限,那麼跳轉到哪一個JSP頁面了數據庫

<!-- 經過unauthorizedUrl指定沒有權限操做時跳轉頁面-->
		<property name="unauthorizedUrl" value="/refuse.jsp" />
複製代碼

到目前爲止,如今問題又來了:apache

一、在applicationContext-shiro.xml中配置過慮器連接,須要將所有的url和權限對應起來進行配置,比較發麻不方便使用。編程

二、每次受權都須要調用realm查詢數據庫,對於系統性能有很大影響,能夠經過shiro緩存來解決。緩存


2、使用註解式和標籤式配置受權

上面的那種方法,仍是須要咱們將所有的url和權限對應起來進行配置,是比較不方便的。咱們可使用受權的另外兩種方式安全

  • 註解式
  • 標籤式

2.1註解式

若是要使用註解式,那麼就必須在Spring中開啓controller類aop支持

<!-- 開啓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>
複製代碼

在Controller中使用註解來進行配置就好了,就不用在咱們的application-shiro中所有集中配置了

//商品信息方法
	@RequestMapping("/queryItems")
	@RequiresPermissions("item:query")//執行queryItems須要"item:query"權限
	public ModelAndView queryItems(HttpServletRequest request) throws Exception {
		
		System.out.println(request.getParameter("id"));
	
		//調用service查詢商品列表
		List<ItemsCustom> itemsList = itemsService.findItemsList(null);

		ModelAndView modelAndView = new ModelAndView();
		modelAndView.addObject("itemsList", itemsList);
		// 指定邏輯視圖名
		modelAndView.setViewName("itemsList");

		return modelAndView;
	}
複製代碼

2.2jsp標籤 受權

這裏寫圖片描述

這裏寫圖片描述

當調用controller的一個方法,因爲該 方法加了@RequiresPermissions("item:query") ,shiro調用realm獲取數據庫中的權限信息,看"item:query"是否在權限數據中存在,若是不存在就拒絕訪問,若是存在就受權經過。

當展現一個jsp頁面時,頁面中若是遇到<shiro:hasPermission name="item:update">,shiro調用realm獲取數據庫中的權限信息,看item:update是否在權限數據中存在,若是不存在就拒絕訪問,若是存在就受權經過。


3、Shiro緩存

針對上邊受權頻繁查詢數據庫,須要使用shiro緩存

3.1緩存流程

shiro中提供了對認證信息和受權信息的緩存。shiro默認是關閉認證信息緩存的,對於受權信息的緩存shiro默認開啓的。主要研究受權信息緩存,由於受權的數據量大。

用戶認證經過。

該用戶第一次受權:調用realm查詢數據庫 該用戶第二次受權:不調用realm查詢數據庫,直接從緩存中取出受權信息(權限標識符)。

3.2使用ehcache和Shiro整合

導入jar包

這裏寫圖片描述

配置緩存管理器,注入到安全管理器中

<!-- 緩存管理器 -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    	<property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>
複製代碼
<!-- securityManager安全管理器 -->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="customRealm" />
		<!-- 注入緩存管理器 -->
		<property name="cacheManager" ref="cacheManager"/>
	</bean>
複製代碼

ehcache的配置文件shiro-ehcache.xml

<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>

複製代碼

3.3緩存清空

若是用戶正常退出,緩存自動清空。 若是用戶非正常退出,緩存自動清空。

還有一種狀況:

  • 當管理員修改了用戶的權限,可是該用戶尚未退出,在默認狀況下**,修改的權限沒法當即生效**。須要手動進行編程實現:在權限修改後調用realm的clearCache方法清除緩存。

清除緩存:

//清除緩存
	public void clearCached() {
		PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
		super.clearCache(principals);
	}
複製代碼

3.4sessionManager

和shiro整合後,使用shiro的session管理,shiro提供sessionDao操做 會話數據。

配置sessionManager

<!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- session的失效時長,單位毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
    </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>
複製代碼

4、驗證碼

在登錄的時候,咱們通常都設置有驗證碼,可是咱們若是使用Shiro的話,那麼Shiro默認的是使用FormAuthenticationFilter進行表單認證。

而咱們的驗證校驗的功能應該加在FormAuthenticationFilter中,在認證以前進行驗證碼校驗

FormAuthenticationFilter是Shiro默認的功能,咱們想要在FormAuthenticationFilter以前進行驗證碼校驗,就須要繼承FormAuthenticationFilter類,改寫它的認證方法

4.1自定義Form認證類

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {

	//原FormAuthenticationFilter的認證方法
	@Override
	protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
		//在這裏進行驗證碼的校驗
		
		//從session獲取正確驗證碼
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpSession session =httpServletRequest.getSession();
		//取出session的驗證碼(正確的驗證碼)
		String validateCode = (String) session.getAttribute("validateCode");
		
		//取出頁面的驗證碼
		//輸入的驗證和session中的驗證進行對比 
		String randomcode = httpServletRequest.getParameter("randomcode");
		if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
			//若是校驗失敗,將驗證碼錯誤失敗信息,經過shiroLoginFailure設置到request中
			httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
			//拒絕訪問,再也不校驗帳號和密碼 
			return true; 
		}
		return super.onAccessDenied(request, response);
	}

		
}
複製代碼

4.2配置自定義類

咱們編寫完自定義類之後,是須要在Shiro配置文件中配置咱們這個自定義類的。

因爲這是咱們自定義的,所以咱們並不須要用戶名就使用username,密碼就使用password,這個也是咱們能夠自定義的

<!-- 自定義form認證過慮器 -->
<!-- 基於Form表單的身份驗證過濾器,不配置將也會註冊此過慮器,表單中的用戶帳號、密碼及loginurl將採用默認值,建議配置 -->
	<bean id="formAuthenticationFilter" class="cn.itcast.ssm.shiro.CustomFormAuthenticationFilter ">
		<!-- 表單中帳號的input名稱 -->
		<property name="usernameParam" value="username" />
		<!-- 表單中密碼的input名稱 -->
		<property name="passwordParam" value="password" />
 </bean>
複製代碼

在Shiro的bean中注入自定義的過濾器

<!-- 自定義filter配置 -->
		<property name="filters">
			<map>
				<!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
				<entry key="authc" value-ref="formAuthenticationFilter" />
			</map>
		</property>
		

複製代碼

在咱們的Controller添加驗證碼錯誤的異常判斷,從咱們的Controller就能夠發現,爲何咱們要把錯誤信息存放在request域對象shiroLoginFailure,由於咱們得在Controller中獲取獲取信息,從而給用戶對應的提示

@RequestMapping("login")
	public String login(HttpServletRequest request)throws Exception{
		
		//若是登錄失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名
		String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
		//根據shiro返回的異常類路徑判斷,拋出指定異常信息
		if(exceptionClassName!=null){
			if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
				//最終會拋給異常處理器
				throw new CustomException("帳號不存在");
			} else if (IncorrectCredentialsException.class.getName().equals(
					exceptionClassName)) {
				throw new CustomException("用戶名/密碼錯誤");
			} else if("randomCodeError".equals(exceptionClassName)){
				throw new CustomException("驗證碼錯誤 ");
			}else {
				throw new Exception();//最終在異常處理器生成未知錯誤
			}
		}
		//此方法不處理登錄成功(認證成功),shiro認證成功會自動跳轉到上一個請求路徑
		//登錄失敗還到login頁面
		return "login";
	}
複製代碼

這裏寫圖片描述

<TR>
		<TD>驗證碼:</TD>
		<TD><input id="randomcode" name="randomcode" size="8" /> <img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align='absMiddle' /> <a href=javascript:randomcode_refresh()>刷新</a></TD>
	</TR>

複製代碼

5、記住我

Shiro還提供了記住用戶名和密碼的功能

用戶登錄選擇「自動登錄」本次登錄成功會向cookie寫身份信息,下次登錄從cookie中取出身份信息實現自動登錄。

想要實現這個功能,咱們的認證信息須要實現Serializable接口

public class ActiveUser implements java.io.Serializable {
	private String userid;//用戶id(主鍵)
	private String usercode;// 用戶帳號
	private String username;// 用戶名稱

	private List<SysPermission> menus;// 菜單
	private List<SysPermission> permissions;// 權限


}
複製代碼

5.1配置rememeber管理器

<!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 -->
	<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
		<property name="cookie" ref="rememberMeCookie" />
	</bean>
	<!-- 記住我cookie -->
	<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
		<!-- rememberMe是cookie的名字 -->
		<constructor-arg value="rememberMe" />
		<!-- 記住我cookie生效時間30天 -->
		<property name="maxAge" value="2592000" />
	</bean>


複製代碼

注入到安全管理器類上

<!-- securityManager安全管理器 -->
	<bean id="securityManager"~~~····
		<property name="cacheManager" ref="cacheManager"/>
		<!-- 注入session管理器 -->
		<property name="sessionManager" ref="sessionManager" />
		<!-- 記住我 -->
		<property name="rememberMeManager" ref="rememberMeManager"/>	
	</bean>



複製代碼

配置頁面的input名稱:

<tr>
				<TD></TD>
				<td><input type="checkbox" name="rememberMe" />自動登錄</td>
			</tr>
複製代碼

若是設置了「記住我」,那麼訪問某些URL的時候,咱們就不須要登錄了。將記住我便可訪問的地址配置讓UserFilter攔截。

<!-- 配置記住我或認證經過能夠訪問的地址 -->
		/index.jsp  = user
		/first.action = user
		/welcome.jsp = user

複製代碼

6、總結

  • Shiro的受權過程和認證過程是相似的,在配置文件上配置須要受權的路徑,當訪問路徑的時候,Shiro過濾器去找到reaml,reaml返回數據之後進行比對。
  • Shiro支持註解式受權,直接在Controller方法上使用註解聲明訪問該方法須要受權
  • Shiro還支持標籤受權,但通常不多用
  • 因爲每次都要對reaml查詢數據庫,性能會低。Shiro默認是支持受權緩存的。爲了達到很好的效果,咱們使用Ehcache來對Shiro的緩存進行管理
  • 配置會話管理器,對會話時間進行控制
  • 手動清空緩存
  • 因爲驗證用戶名和密碼以前,通常須要驗證驗證碼的。因此,咱們要改寫表單驗證的功能,先讓它去看看驗證碼是否有錯,若是驗證碼有錯的話,那麼用戶名和密碼就不用驗證了。
  • 將自定義的表單驗證類配置起來。
  • 使用Shiro提供的記住我功能,若是用戶已經認證了,那就不用再次登錄了。能夠直接訪問某些頁面。

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同窗,能夠關注微信公衆號:Java3y

相關文章
相關標籤/搜索