shiro權限驗證框架

shiro權限驗證框架

1.什麼是Shiro?

    Shiro 是一個用 Java 語言實現的框架,通過一個簡單易用的 API 提供身份驗證和授權。使用 Shiro,您就能夠爲您的應用程序提供安全性而又無需從頭編寫所有代碼。

2.爲什麼要用Shiro?

    shiro在大多數的企業級系統中,我們一般都是採用角色關聯資源,然後對用戶指定一些角色,這樣用戶就擁有了一些url,菜單等資源,登陸後頁面即可相應的一些功能,但是這種情況下存在這安全問題,因爲用戶頁面只是沒有顯示相應的功能菜單,若某個用戶知道url地址,直接去訪問,系統是沒法控制該用戶操作的,於是便迎來了權限驗證框架這一說。

3.Shiro能夠有哪些方式控制權限呢?

   同樣shiro有兩種配置方式,xml和註解,當然xml相對而言不靈活,只能指定經過認證授權後可以訪問哪些頁面以及不能訪問哪些頁面,對於註解就靈活一些了,可以適應於各種情景,
方法上加上
@RequiresAuthentication  (驗證用戶是否登錄)
@RequiresUser  驗證用戶是否被記憶,user有兩種含義:一種是成功登錄的(subject.isAuthenticated() 結果爲true);另外一種是被記憶的(subject.isRemembered()結果爲true)。
@RequiresGuest 驗證是否是一個guest的請求
@RequiresRoles 如果subject中有aRoleName角色纔可以訪問方法someMethod。如果沒有這個權限則會拋出異常AuthorizationException。
@RequiresPermissions  要求subject中必須同時含有file:read和write:aFile.txt的權限才能執行方法someMethod()。否則拋出異常AuthorizationException。


4.說說怎樣使用Shiro?


先添加 spring-shiro.xml 配置
具體如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-
instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd"
	default-lazy-init="true">
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
	<property name="securityManager" ref="securityManager" />
	<property name="loginUrl" value="/shiro/turnlogin.do" />
	<property name="successUrl" value="/shiro/success.do" />
	<property name="unauthorizedUrl" value="/shiro/unauth.do" />
	<property name="filterChainDefinitions">
		<value>
			/shiro/success = authc <!-- authc 表示需要認證才能訪問的頁面 -->
			/shiro/success = authc, perms[/home]  <!-- perms 表示需要該權限才能訪問的頁面 -->
		</value>
	</property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
	<property name="realm" ref="myShiroRealm"></property>
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

<bean id="myShiroRealm" class="com.hfmx.util.shiro.MyShiroRealm">
	<!-- businessManager 用來實現用戶名密碼的查詢 -->
	<!-- <property name="shiroService" ref="shiroService" />  -->
</bean>
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
      <property name="exceptionMappings">
          <props>
              <!--登錄-->
              <prop key="org.apache.shiro.authz.UnauthenticatedException">
                  redirect:/shiro/turnlogin.do
              </prop>
              <!--授權-->
              <prop key="org.apache.shiro.authz.UnauthorizedException">
              	  redirect:/shiro/turnlogin.do
              </prop>
          </props>
      </property>
      <property name="defaultErrorView" value="s/403" />
</bean>
</beans>


在applicationContext中引入該文件
<import resource="classpath*:/spring-shiro.xml" />



在springMVC中加入:
<!-- 開啓Shiro的註解,實現對Controller的方法級權限檢查(如@RequiresRoles,@RequiresPermissions),需
藉助SpringAOP掃描使用Shiro註解的類,並在必要時進行安全邏輯驗證 -->  
<!-- 需要在 sprimg-MVC的配置文件中 -->    
<bean id="controllerAdvisorAutoProxyCreator" 
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-
on="lifecycleBeanPostProcessor"/>
<beanid="controllerAuthorizationAttributeSourceAdvisor"  class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>



上述這樣就配置完畢了

我們要新建一個類MyShiroRealm ,繼承AuthorizingRealm

重寫doGetAuthorizationInfo() //獲取授權信息
以及doGetAuthenticationInfo()//獲取認證信息
這兩個方法

因爲公司系統 已經使用了一定時間了,用戶登錄這塊已經很完善就仍然使用原來的,所以這裏的doGetAuthenticationInfo方法 可以不做太多的處理,這裏也把應有的流程貼出來
	@Override
	public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException {
		
		//1. 把AuthenticationToken 轉化爲 UsernamePasswordToken
			UsernamePasswordToken token = (UsernamePasswordToken) at;
			
		//2. 從UsernamePasswordToken 中獲取 username
			String username = token.getUsername();
			char[] password = token.getPassword();
			
		//3. 調用數據庫的方法,從數據庫中查詢 username 對應的用戶記錄

			
		//4. 若用戶不存在,則可以拋出 UnknownAccountException
		
			
		//5. 根據用戶信息的情況,決定是否需要拋出其他的AuthenticationException 異常
		
		//6. 根據用戶的情況,來構建 AuthenticationInfo 對象並返回
			
		//不帶 鹽值的 加密 -- 可能存在密碼相同時  加密後的密文也是相同的
		return new SimpleAuthenticationInfo(username, new String(password), getName());
	
		//帶鹽值加密 -- 最安全
		//ByteSource credentialsSalt = ByteSource.Util.bytes(user.getUsername());
		//return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), credentialsSalt, getName());
	}



因爲公司的系統 用戶登陸後,擁有的資源保存在session中,於是有了這樣一個想法,將這些資源再交給shiro框架,讓它來限制住用戶只能訪問自己的資源,那麼就使用@RequiresPermissions ("url地址")  註解方式,可以達到最完美的權限控制,這裏也貼一下代碼:
@Override
	public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
		//1. 從PrincipalCollection 中獲取登錄用戶的信息
		Object principal = pc.getPrimaryPrincipal();
		
		//2. 利用登錄用戶的信息來獲取當前用戶的角色或權限(可能需要查詢數據庫)
		
		
		//3. 創建SimpleAuthorizationInfo,並設置roles屬性
		
		
		//4. 返回SimpleAuthorizationInfo對象

		
		//通過從shiro  session值中獲取信息
		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}

		
		
		/*// 根據自己系統規則的需要編寫獲取授權信息,這裏只獲取了用戶對應角色的資源url信息
		String username = (String) pc.fromRealm(getName()).iterator().next();
		
		//指定url訪問     通過查找數據庫的方式進行
		if (username != null) {
			ArrayList<String> urls = shiroService.getUrlByName(username);
			if(null!=urls&&urls.size()!=0){
				SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
				for (String url : urls) {
					if(null!=url&&(!(url.trim()).equals(""))){
						info.addStringPermission(url);
					}
				}
				return info;
			}
		}*/
		
		return null;
	}


其實註解驗證的方式原理很簡單,通過doGetAuthorizationInfo(PrincipalCollection pc)方法參數pc可以獲得到用戶的登錄名,通過登錄名來查找該用戶的url資源,然後將這些url資源 添加至SimpleAuthorizationInfo中(通過info.addStringPermission(url)方法),然後在controller層的方法上加上@RequiresPermissions ("url地址")  註解方式,注意這裏的url地址需要與真實的RequestMapping映射地址一致,用戶請求了該地址後系統就會自動匹配剛剛添加的SimpleAuthorizationInfo中是否含有該url地址,如果有則通過,反之拋出異常。


那麼PrincipalCollection pc 的參數是誰傳過來的呢?其實在登錄時成功後,就需要進行設置了,

if(!currentUser.isAuthenticated()){
 UsernamePasswordToken token = new UsernamePasswordToken(_userName, password); 
 token.setRememberMe(true);
try{
  currentUser.login(token);   //添加用戶信息
}catch(AuthenticationException ac){
System.out.println("登錄異常:" + ac.getMessage());
this.writeJson(response, new AjaxMsg(false, "登錄異常:" + ac.getMessage()));
   }
}


5. 最後說一下,這幾天解決的問題,

doGetAuthorizationInfo()是獲取授權的方法,通常我們是通過用戶名查詢數據庫的方式獲取所有資源,然後包裝成SimpleAuthorizationInfo對象進行返回,顯然這種方式不太可取,每次認證都要查詢數據庫,肯定是不行的,這裏shiro提供了緩存技術可以查閱一下,由於該系統是支持集羣的,所以這種方式也不適合,而該系統中session交由redis進行管理,所以有個想法,把這些資源放進session,也就是redis,這樣獲取的時候能夠保證高效。最有趣的事來了,
先前測試案例中我是將 資源放到Httpsession中,然後在doGetAuthorizationInfo()方法中獲取,而這裏獲取的是org.apache.shiro.session.Session,需要通過轉化才能成爲httpsession,但是效果達到了,後來到正式使用的時候,發現獲取不到了。

找啊找.........突然發現可以直接把資源放到org.apache.shiro.session.Session中,這樣豈不是不用轉化了,於是登陸成功後  執行
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute("url", list);	
將url資源添加至org.apache.shiro.session.Session中,

然後在doGetAuthorizationInfo()是獲取授權的方法  直接 拿出來進行包裝,
		//通過從shiro  session值中獲取信息
		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}
問題解決了,先就記錄到這兒吧,其實還有進一步的優化。




補充:!!!針對上述 稱述有問題的地方特別指出

上述說的shiro是部署在集羣系統中的,那麼在登錄時候直接將url資源放置到org.apache.shiro.session.Session中是不合理的,因爲集羣下,shiro的session不依懶於任何容器,也不會自動像HttpSession一樣通過spring-session自動放置到redis中管理,於是集羣下這種方式就行不通了,那麼該怎麼辦呢?

其實 最初的時候已經 做了,那就是登陸成功後 直接 
request.getSession().setAttribute("url", list);   將url資源也直接放置到httpsession中,這樣一切都變得簡單了,session週期也不用自己去管理了,
然後在doGetAuthorizationInfo()方法中獲取剛剛放進去的資源,也就是HttpSession的一個Attribute爲‘’url‘’的值

HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
List<String> urllist = (List<String>)request.getSession().getAttribute("url");

即可獲得HttpSession
於是也就支持了集羣和非集羣下的使用!


總結一下:大致有三種方式 權限驗證時獲取 資源值

		//1. 通過從shiro  session  直接獲取登錄時存放的url資源   (非集羣 下 使用)
/*		List<String> urllist = (List<String>) SecurityUtils.getSubject().getSession().getAttribute("url");
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
				}
			}
			return info;
		}*/

		
		
		//2. 通過 request的session獲取登錄時存放的url資源     (集羣 非集羣 下 通用--效率高)
		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
		List<String> urllist = (List<String>)request.getSession().getAttribute("url");
		
		if(null!=urllist&&urllist.size()!=0){
			SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
			for (String url : urllist) {
				if(null!=url&&(!(url.trim()).equals(""))){
					info.addStringPermission(url);
					System.err.println("添加資源"+url);
				}
			}
			return info;
		}
		
		
		//3. 根據自己系統規則的需要編寫獲取授權信息,這裏只獲取了用戶對應角色的資源url信息  (集羣 非集羣 下 通用--效率低下)
		/*String username = (String) pc.fromRealm(getName()).iterator().next();
		
		//指定url訪問     通過查找數據庫的方式進行
		if (username != null) {
			ArrayList<String> urls = shiroService.getUrlByName(username);
			if(null!=urls&&urls.size()!=0){
				SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
				for (String url : urls) {
					if(null!=url&&(!(url.trim()).equals(""))){
						info.addStringPermission(url);
					}
				}
				return info;
			}
		}*/


到此 最完美的解決方法也就是 (第二種)資源還是  放在 HttpSession 中 最合理!

--謝謝您的閱讀,不足地方請指出!








發佈了48 篇原創文章 · 獲贊 74 · 訪問量 22萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章