Shiro【受權、整合Spirng、Shiro過濾器】

前言

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

  • Shiro受權的方式簡單介紹
  • 與Spring整合
  • 初始Shiro過濾器

1、Shiro受權

上一篇咱們已經講解了Shiro的認證相關的知識了,如今咱們來弄Shiro的受權java

Shiro受權的流程和認證的流程實際上是差很少的:程序員

這裏寫圖片描述

1.1Shiro支持的受權方式

Shiro支持的受權方式有三種:web

Shiro 支持三種方式的受權:
編程式:經過寫if/else 受權代碼塊完成:
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(「admin」)) {
//有權限
} else {
//無權限
}
註解式:經過在執行的Java方法上放置相應的註解完成:
@RequiresRoles("admin")
public void hello() {
//有權限
}
JSP/GSP 標籤:在JSP/GSP 頁面經過相應的標籤完成:
<shiro:hasRole name="admin">
<!— 有權限—>
</shiro:hasRole>
複製代碼

1.2使用編程式受權

一樣的,咱們是經過安全管理器來去受權的,所以咱們仍是須要配置對應的配置文件的:spring

shiro-permission.ini配置文件:數據庫

#用戶
[users]
#用戶zhang的密碼是123,此用戶具備role1和role2兩個角色
zhang=123,role1,role2
wang=123,role2

#權限
[roles]
#角色role1對資源user擁有create、update權限
role1=user:create,user:update
#角色role2對資源user擁有create、delete權限
role2=user:create,user:delete
#角色role3對資源user擁有create權限
role3=user:create



#權限標識符號規則:資源:操做:實例(中間使用半角:分隔)
user:create:01  表示對用戶資源的01實例進行create操做。
user:create:表示對用戶資源進行create操做,至關於user:create:*,對全部用戶資源實例進行create操做。
user:*:01  表示對用戶資源實例01進行全部操做。


複製代碼

代碼測試:apache

// 角色受權、資源受權測試
	@Test
	public void testAuthorization() {

		// 建立SecurityManager工廠
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-permission.ini");

		// 建立SecurityManager
		SecurityManager securityManager = factory.getInstance();

		// 將SecurityManager設置到系統運行環境,和spring後將SecurityManager配置spring容器中,通常單例管理
		SecurityUtils.setSecurityManager(securityManager);

		// 建立subject
		Subject subject = SecurityUtils.getSubject();

		// 建立token令牌
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"123");

		// 執行認證
		try {
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("認證狀態:" + subject.isAuthenticated());
		// 認證經過後執行受權

		// 基於角色的受權
		// hasRole傳入角色標識
		boolean ishasRole = subject.hasRole("role1");
		System.out.println("單個角色判斷" + ishasRole);
		// hasAllRoles是否擁有多個角色
		boolean hasAllRoles = subject.hasAllRoles(Arrays.asList("role1",
				"role2", "role3"));
		System.out.println("多個角色判斷" + hasAllRoles);

		// 使用check方法進行受權,若是受權不經過會拋出異常
		// subject.checkRole("role13");

		// 基於資源的受權
		// isPermitted傳入權限標識符
		boolean isPermitted = subject.isPermitted("user:create:1");
		System.out.println("單個權限判斷" + isPermitted);

		boolean isPermittedAll = subject.isPermittedAll("user:create:1",
				"user:delete");
		System.out.println("多個權限判斷" + isPermittedAll);
		// 使用check方法進行受權,若是受權不經過會拋出異常
		subject.checkPermission("items:create:1");

	}

複製代碼

1.3自定義realm進行受權

通常地,咱們的權限都是從數據庫中查詢的,並非根據咱們的配置文件來進行配對的。所以咱們須要自定義reaml,讓reaml去對比的是數據庫查詢出來的權限編程

shiro-realm.ini配置文件:將自定義的reaml信息注入到安全管理器中安全

[main]
#自定義 realm
customRealm=cn.itcast.shiro.realm.CustomRealm
#將realm設置到securityManager,至關 於spring中注入
securityManager.realms=$customRealm




複製代碼

咱們上次已經使用過了一個自定義reaml,當時候僅僅重寫了doGetAuthenticationInfo()方法,此次咱們重寫doGetAuthorizationInfo()方法微信

// 用於受權
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) {
		
		//從 principals獲取主身份信息
		//將getPrimaryPrincipal方法返回值轉爲真實身份類型(在上邊的doGetAuthenticationInfo認證經過填充到SimpleAuthenticationInfo中身份類型),
		String userCode =  (String) principals.getPrimaryPrincipal();
		
		//根據身份信息獲取權限信息
		//鏈接數據庫...
		//模擬從數據庫獲取到數據
		List<String> permissions = new ArrayList<String>();
		permissions.add("user:create");//用戶的建立
		permissions.add("items:add");//商品添加權限
		//....
		
		//查到權限數據,返回受權信息(要包括 上邊的permissions)
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		//將上邊查詢到受權信息填充到simpleAuthorizationInfo對象中
		simpleAuthorizationInfo.addStringPermissions(permissions);

		return simpleAuthorizationInfo;
	}

複製代碼

測試程序:

// 自定義realm進行資源受權測試
	@Test
	public void testAuthorizationCustomRealm() {

		// 建立SecurityManager工廠
		Factory<SecurityManager> factory = new IniSecurityManagerFactory(
				"classpath:shiro-realm.ini");
		// 建立SecurityManager
		SecurityManager securityManager = factory.getInstance();
		// 將SecurityManager設置到系統運行環境,和spring後將SecurityManager配置spring容器中,通常單例管理
		SecurityUtils.setSecurityManager(securityManager);
		// 建立subject
		Subject subject = SecurityUtils.getSubject();

		// 建立token令牌
		UsernamePasswordToken token = new UsernamePasswordToken("zhangsan",
				"111111");
		// 執行認證
		try {
			subject.login(token);
		} catch (AuthenticationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("認證狀態:" + subject.isAuthenticated());
		// 認證經過後執行受權

		// 基於資源的受權,調用isPermitted方法會調用CustomRealm從數據庫查詢正確權限數據
		// isPermitted傳入權限標識符,判斷user:create:1是否在CustomRealm查詢到權限數據以內
		boolean isPermitted = subject.isPermitted("user:create:1");
		System.out.println("單個權限判斷" + isPermitted);

		boolean isPermittedAll = subject.isPermittedAll("user:create:1",
				"user:create");
		System.out.println("多個權限判斷" + isPermittedAll);

		// 使用check方法進行受權,若是受權不經過會拋出異常
		subject.checkPermission("items:add:1");

	}
複製代碼

這裏寫圖片描述


2、Spring與Shiro整合

2.1導入jar包

  • shiro-web的jar、
  • shiro-spring的jar
  • shiro-code的jar

這裏寫圖片描述

2.2快速入門

shiro也經過filter進行攔截。filter攔截後將操做權交給spring中配置的filterChain(過慮鏈兒)

在web.xml中配置filter

<!-- shiro的filter -->
	<!-- shiro過慮器,DelegatingFilterProxy經過代理模式將spring容器中的bean和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>
複製代碼

applicationContext-shiro.xml 中配置web.xml中fitler對應spring容器中的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,建議不配置,shiro認證成功自動到上一個請求路徑 -->
		<!-- <property name="successUrl" value="/first.action"/> -->
		<!-- 經過unauthorizedUrl指定沒有權限操做時跳轉頁面-->
		<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>
				<!--全部url均可以匿名訪問-->
				/** = anon
			</value>
		</property>
	</bean>
複製代碼

配置安全管理器

<!-- securityManager安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="customRealm" />	
</bean>
複製代碼

配置reaml

<!-- realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
</bean>
複製代碼

步驟:

  • 在web.xml文件中配置shiro的過濾器
  • 在對應的Spring配置文件中配置與之對應的filterChain(過慮鏈兒)
  • 配置安全管理器,注入自定義的reaml
  • 配置自定義的reaml

2.3靜態資源不攔截

咱們在spring配置過濾器鏈的時候,咱們發現這麼一行代碼:

<!--全部url均可以匿名訪問 -->
 	/** = anon
複製代碼

anon其實就是shiro內置的一個過濾器,上邊的代碼就表明着全部的匿名用戶均可以訪問

固然了,後邊咱們還須要配置其餘的信息,爲了讓頁面可以正常顯示,咱們的靜態資源通常是不須要被攔截的

因而咱們能夠這樣配置:

<!-- 對靜態資源設置匿名訪問 -->
	/images/** = anon
	/js/** = anon
	/styles/** = anon
複製代碼

3、初識shiro過濾器

上面咱們瞭解到了anno過濾器的,shiro還有其餘的過濾器的..咱們來看看

這裏寫圖片描述

經常使用的過濾器有下面幾種:

anon:例子/admins/**=anon 沒有參數,表示能夠匿名使用。 authc:例如/admins/user/**=authc表示須要認證(登陸)才能使用,FormAuthenticationFilter是表單認證,沒有參數 perms:例子/admins/user/**=perms[user:add:*],參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每一個參數都經過才經過,想當於isPermitedAll()方法。 user:例如/admins/user/**=user沒有參數,表示必須存在用戶, 身份認證經過或經過記住我認證經過的能夠訪問,當登入操做時不作檢查

3.1登錄與退出

使用FormAuthenticationFilter過慮器實現 ,原理以下:

  • 當用戶沒有認證時,請求loginurl進行認證【上邊咱們已經配置了】,用戶身份和用戶密碼提交數據到loginurl
  • FormAuthenticationFilter攔截住取出request中的username和password(兩個參數名稱是能夠配置的
  • FormAuthenticationFilter 調用realm傳入一個token(username和password)
  • realm認證時根據username查詢用戶信息(在Activeuser中存儲,包括 userid、usercode、username、menus)。
  • 若是查詢不到,realm返回null,FormAuthenticationFilter向request域中填充一個參數(記錄了異常信息)
  • 查詢出用戶的信息以後,FormAuthenticationFilter會自動將reaml返回的信息和token中的用戶名和密碼對比。若是不對,那就返回異常。

3.1.1登錄頁面

因爲FormAuthenticationFilter的用戶身份和密碼的input的默認值(username和password)修改頁面的帳號和密碼的input的名稱爲username和password

<TR>
		<TD>用戶名:</TD>
		<TD colSpan="2"><input type="text" id="usercode" name="username" style="WIDTH: 130px" /></TD>
	</TR>
	<TR>
		<TD>密 碼:</TD>
		<TD><input type="password" id="pwd" name="password" style="WIDTH: 130px" />
		</TD>
	</TR>
複製代碼

3.1.2登錄代碼實現

上面咱們已經說了,當用戶沒有認證的時候,請求的loginurl進行認證,用戶身份的用戶密碼提交數據到loginrul中

當咱們提交到loginurl的時候,表單過濾器會自動解析username和password去調用realm來進行認證。最終在request域對象中存儲shiroLoginFailure認證信息,若是返回的是異常的信息,那麼咱們在login中拋出異常便可

//登錄提交地址,和applicationContext-shiro.xml中配置的loginurl一致
	@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";
	}
複製代碼

配置認證過濾器

<value>
		<!-- 對靜態資源設置匿名訪問 -->
		/images/** = anon
		/js/** = anon
		/styles/** = anon

		<!-- /** = authc 全部url都必須認證經過才能夠訪問-->
		/** = authc
	</value>

複製代碼

3.2退出

不用咱們去實現退出,只要去訪問一個退出的url(該 url是能夠不存在),由LogoutFilter攔截住,清除session。

在applicationContext-shiro.xml配置LogoutFilter:

<!-- 請求 logout.action地址,shiro去清除session-->
		/logout.action = logout
複製代碼

4、認證後信息在頁面顯示

一、認證後用戶菜單在首頁顯示 二、認證後用戶的信息在頁頭顯示

realm從數據庫查詢用戶信息,將用戶菜單、usercode、username等設置在SimpleAuthenticationInfo中。

//realm的認證方法,從數據庫查詢用戶信息
	@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) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}

		// 若是查詢不到返回null
		if(sysUser==null){//
			return null;
		}
		// 從數據庫查詢到密碼
		String password = sysUser.getPassword();
		
		//鹽
		String salt = sysUser.getSalt();

		// 若是查詢到返回認證信息AuthenticationInfo
		
		//activeUser就是用戶身份信息
		ActiveUser activeUser = new ActiveUser();
		
		activeUser.setUserid(sysUser.getId());
		activeUser.setUsercode(sysUser.getUsercode());
		activeUser.setUsername(sysUser.getUsername());
		//..
		
		//根據用戶id取出菜單
		List<SysPermission> menus  = null;
		try {
			//經過service取出菜單 
			menus = sysService.findMenuListByUserId(sysUser.getId());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//將用戶菜單 設置到activeUser
		activeUser.setMenus(menus);

		//將activeUser設置simpleAuthenticationInfo
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
				activeUser, password,ByteSource.Util.bytes(salt), this.getName());

		return simpleAuthenticationInfo;
	}
複製代碼

配置憑配器,由於咱們用到了md5和散列

<!-- 憑證匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
	<property name="hashAlgorithmName" value="md5" />
	<property name="hashIterations" value="1" />
</bean>
複製代碼
<!-- realm -->
<bean id="customRealm" class="cn.itcast.ssm.shiro.CustomRealm">
	<!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
	<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
複製代碼

在跳轉到首頁的時候,取出用戶的認證信息,轉發到JSP便可

//系統首頁
	@RequestMapping("/first")
	public String first(Model model)throws Exception{
		
		//從shiro的session中取activeUser
		Subject subject = SecurityUtils.getSubject();
		//取身份信息
		ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
		//經過model傳到頁面
		model.addAttribute("activeUser", activeUser);
		
		return "/first";
	}
複製代碼

5、總結

  • Shiro用戶權限有三種方式
    • 編程式
    • 註解式
    • 標籤式
  • Shiro的reaml默認都是去找配置文件的信息來進行受權的,咱們通常都是要reaml去數據庫來查詢對應的信息。所以,又須要自定義reaml
  • 整體上,認證和受權的流程差很少。
  • Spring與Shiro整合,Shiro實際上的操做都是經過過濾器來乾的。Shiro爲咱們提供了不少的過濾器。
    • 在web.xml中配置Shiro過濾器
    • 在Shiro配置文件中使用web.xml配置過的過濾器。
  • 配置安全管理器類,配置自定義的reaml,將reaml注入到安全管理器類上。將安全管理器交由Shiro工廠來進行管理。
  • 在過濾器鏈中設置靜態資源不攔截。
  • 在Shiro使用過濾器來進行用戶認證,流程是這樣子的:
    • 配置用於認證的請求路徑
    • 當訪問程序員該請求路徑的時候,Shiro會使用FormAuthenticationFilter會調用reaml得到用戶的信息
    • reaml能夠拿到token,經過用戶名從數據庫獲取獲得用戶的信息,若是用戶不存在則返回null
    • FormAuthenticationFilter會將reaml返回的數據進行對比,若是不一樣則拋出異常
    • 咱們的請求路徑僅僅是用來檢測有沒有異常拋出,並不用來作校驗的。
  • shiro還提供了退出用戶的攔截器,咱們配置一個url就好了。
  • 當須要獲取用戶的數據用於回顯的時候,咱們能夠在SecurityUtils.getSubject()來獲得主體,再經過主體拿到身份信息。

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

相關文章
相關標籤/搜索