1. 程式人生 > >【原創】基於Spring-SpringMVC-Mybatis 的 Shiro 安全框架使用教程--轉載請註明出處

【原創】基於Spring-SpringMVC-Mybatis 的 Shiro 安全框架使用教程--轉載請註明出處

Shiro使用說明文件

宣告:

我們所使用的框架為SSM框架+Shiro許可權控制框架,在以下部分中,將會描述如何使用一個Shiro框架。

框架使用概述:

Shiro安全框架為我們提供了一個較為完善的許可權管理系統。我們將使用該框架提供的使用者登入,登出,使用者許可權控制,使用者資訊儲存。

首先我們要明確一個物件Subject,這個物件可以是一個使用者,一個爬蟲,也可是一個人工智慧等任意訪問該網站的物件。我們對其許可權進行管理的一切資訊,都是基於Subject物件得來的。而Subject物件可以通過SecurityUtils.getSubject()來獲得。

Shiro中還提供給了我們一個獨特的SessionShiro中的SessionHttpSession不同,他們的不同之處在於HttpSession當伺服器重啟後就會失效,而Session則不會。這裡的Session可以通過Subject物件呼叫getSession()方法來獲取Session,而失效時間可以通過setTimeout(long time)方法來設定,其中的引數為一個long型數字,當long<1000時,則會使Session永遠有效。

後端的許可權控制:

其最常使用的註解是Shiro@RequiresRoles註解和@RequiresPermissions註解。@RequiresRoles

註解限定了一個類或者方法需要哪一些角色型別才能夠進行訪問,@RequiresPermissions註解限定了類或方法需要哪一些許可權型別才能夠被訪問。如果沒有沒有該許可權,則會被攔截,之後跳轉到指定的登入頁面上。

如下圖中程式碼所示,下圖中的程式碼沒有對角色進行限定,僅對許可權進行了限定:

	/**
	 * 跳轉到使用者的介面
	 * @RequiresPermissions 該註解表示只有存在該許可權的情況下才能訪問該頁面
	 * @param request
	 * @return 跳轉至user.jsp
	 */
	@RequiresPermissions(value={"view:aaaa"}, logical=Logical.OR)  
	@RequestMapping("user.do")
	public String toUser(HttpServletRequest request){
		Subject user = SecurityUtils.getSubject(); 
		user.getSession().setTimeout(1000000);
		//該方法可以用於獲取使用者的獨特的識別資訊,一般為使用者名稱
		String userName = (String) user.getPrincipal();
		request.setAttribute("userName", userName);
		return "user.jsp";
	}

@RequiresPermissions(value={"view:aaaa","aaaa"}, logical=Logical.OR)

該註解限定了使用者是否具有某個許可權,logical=Logical.OR表示該註解標註的許可權之間的關係為|關係,當logical=Logical.AND時表示許可權之間的關係為& 關係。而viewaaaa,表示使用者只要具有view許可權下的aaaa許可權即可訪問該方法或者類,而如果使用者具有viewaaaa許可權,而不具有view許可權,那麼當value={"view"}時是無法訪問的。當用戶具有view許可權時,則可以訪問所有的view許可權下的方法或者類。

”分號,設定許可權為子許可權與父許可權之間的關係,此時的許可權view即是父許可權,而viewaaaa為子許可權。

自動的頁面攔截:

Shiro提供了自動的頁面攔截,在ShiroConfig.xml中配置了哪些是需要進行許可權攔截的頁面,哪些是不需要進行許可權攔截的頁面。我們的專案為內網專案,所以以後我們將只暴露極少的登入介面允許訪問,其他介面全部都將劃分在Shiro的保護之下。

在該demo專案中,只有login.*和index.*是可以被訪問的,其他頁面均是受保護的。

	<!-- 配置shiroFilter 6.1 id必須和web.xml 檔案中配置的DelegatingFilterProxy的filter-name一致 -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!--
		<property name="loginUrl" value="/login.jsp" />
		<property name="successUrl" value="/index.jsp" />
		<property name="unauthorizedUrl" value="/login.jsp" />
		-->
		<!-- 配置哪些頁面需要受保護 , anon可以被匿名訪問,authc 必須認證之後才能訪問 -->
		<property name="filterChainDefinitions">
			<value>
				/login.* = anon
				/index.* = anon
				/** = authc
			</value>
		</property>
	</bean>

登入驗證與授權:

Shiro的登入驗證與授權中,需要new一個token物件,然後通過SecurityUtil類來獲取subject物件,然後呼叫subjectlogin方法來進行登入。(程式碼來自類:controller下的)

	@RequestMapping("login.do")
	public String checkLogin(String username, String password, HttpServletRequest request) {  
        try{
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);  
            Subject currentUser = SecurityUtils.getSubject();  
            //判斷該使用者是否已經被驗證,如果已經驗證,則不需要再次驗證
            if (!currentUser.isAuthenticated()){
                //使用shiro來驗證  
            	//設定是否要儲存賬戶資訊(因為本身該專案具有對其他人保密性,因此不會使用)
//            token.setRememberMe(true);  
            	token.setRememberMe(false); 
            	currentUser.login(token);//驗證角色和許可權  
            } 
        }catch(Exception e){
        	//打印出異常資訊,然後跳轉到登入頁面(任何情況下的表單均要使用ajax,而非form提交,不可採取該方式)
        	e.printStackTrace();
        	return "login.jsp";  
        }
        return "index.jsp";  
    }  

在登入時,所呼叫的登入方法,和所呼叫的授權方法的程式碼是需要自己來進行編寫的。

登入以及授權程式碼如下

package study;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

import study.dao.UserDao;

public class MyShiroRealm extends AuthorizingRealm implements Realm{
	
	@Autowired
	private UserDao userDao;
	/**
	 * 對使用者進行授權處理
	 * 這個方法在使用者登入的時候會進行執行,對使用者進行授權
	 * 該方法中可以通過從資料庫獲取角色,從資料庫獲取許可權的形式來進行對角色及許可權的授權
	 * 在從資料庫中獲取許可權資訊時,如下方法,登陸時獲取使用者資訊相同
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
	Set<String> roleNames = new HashSet<String>();  
        Set<String> permissions = new HashSet<String>();  
        roleNames.add("admin");//新增角色
       	permissions.add("view:aaaa");  //新增許可權
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);  
        info.setStringPermissions(permissions);  
        return info;  
	}
	
	/**
	 * 驗證使用者是否能夠進行登入
	 * 此處呼叫了持久層的使用者表資訊,在這裡進行了驗證使用者是否可以登入,是否為使用者表中的使用者
	 * 在實際使用中,密碼將使用MD5進行加密,此處未使用
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) 
			throws AuthenticationException {
		//該token為controller中所傳入的token,所以可以直接強轉
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		//從token中獲取到使用者名稱和密碼
		String userName = token.getUsername();
		String password = new String(token.getPassword());
		HashMap returnMap = null;
		try{
			//呼叫持久層,查詢使用者名稱是否存在
			HashMap<String, Object> paramsMap = new HashMap<String, Object>();
			paramsMap.put("userName", userName);
			returnMap = userDao.selectUser(paramsMap);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		//存在後返回一個物件,包含使用者名稱和密碼,對使用者名稱和密碼進行驗證
        if(password.equals(returnMap.get("password"))){
        	//如果存在該使用者,則返回一個使用者資訊物件,此處使用者主要資訊傳入為userName,憑證傳入為password(可傳任意物件)
            return new SimpleAuthenticationInfo(userName, password, getName());  
        }else{
        	//如果沒有該使用者,則丟擲一個異常(此處必須丟擲異常)
            throw new AuthenticationException();  
        }
	}
}

要注意我們最終返回的物件 new SimpleAuthenticationInfo 的構造器中,第一個引數為主要資訊,第二個引數為使用者的憑證,第三個引數為RealName,呼叫父類的getName()方法即可。其中前兩個引數可傳Object型別,第一個引數主要資訊可以在其他程式碼中,通過Subject物件來進行獲取。

前端許可權控制標籤:

在前端,shiro框架也提供了一套jstl標籤,將標籤庫匯入後,就可以使用該標籤來實現許可權控制。前端程式碼如下:

	<!-- 使用shiro的jstl標籤,可以直接對許可權進行控制 -->
	<!-- 根據使用者的許可權資訊判斷是否存在該部分內容 -->
	<shiro:hasPermission name="view2">
		<h1>因為你沒有許可權view2,所以看不到這行字</h1>
	</shiro:hasPermission>
	
	<!-- 根據使用者的許可權資訊判斷是否存在該部分內容 -->
	<shiro:hasPermission name="view,view:aaaa">
		<h1>hasPermission  "erty,view:aaaa"</h1>
	</shiro:hasPermission>

在使用該標籤前,需要先匯入標籤庫:

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

前端jstl標籤使用問題:

在使用jstl標籤時,打出<shiro開啟提示就可以看到下方有如下的shiro標籤提示:


其中的所有的標籤中的name,許可權均應使用“,”逗號進行分隔,同時適用“:”冒號。使用的大致情況與註解一致。

Authenticated:當用戶已認證時,內部的資訊將會顯示

Guest:來賓(未登入者),內部資訊將會顯示

hashAnyRoles:存在name中的角色中的一個時,內部資訊將會顯示

hasPermission:存在name中的所有許可權時,內部資訊將會顯示

hasRole:存在name中的所有許可權時,內部資訊將會顯示

lacksPermission:不存在name中的所有許可權時,內部資訊將會顯示

lacksRole:不存在name中所有角色時,內部資訊將會顯示

nowAuthenticated:沒有被認證時,內部資訊將會顯示

User:當用戶登入後,內部資訊將會顯示

Principal:返回當前使用者的資訊中的某資料,該資訊會從主要資訊中進行獲取。

Property 其中填寫該物件中的哪一個屬性

Type其中填寫主要資訊的型別,如study.User(Model)

defaultValue填寫當值為空時的預設值


如果該部落格中存在錯誤,請在評論處指出。