【原創】基於Spring-SpringMVC-Mybatis 的 Shiro 安全框架使用教程--轉載請註明出處
Shiro使用說明文件
宣告:
我們所使用的框架為SSM框架+Shiro許可權控制框架,在以下部分中,將會描述如何使用一個Shiro框架。
框架使用概述:
Shiro安全框架為我們提供了一個較為完善的許可權管理系統。我們將使用該框架提供的使用者登入,登出,使用者許可權控制,使用者資訊儲存。
首先我們要明確一個物件Subject,這個物件可以是一個使用者,一個爬蟲,也可是一個人工智慧等任意訪問該網站的物件。我們對其許可權進行管理的一切資訊,都是基於Subject物件得來的。而Subject物件可以通過SecurityUtils.getSubject()來獲得。
後端的許可權控制:
其最常使用的註解是Shiro的@RequiresRoles註解和@RequiresPermissions註解。@RequiresRoles
如下圖中程式碼所示,下圖中的程式碼沒有對角色進行限定,僅對許可權進行了限定:
/** * 跳轉到使用者的介面 * @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時表示許可權之間的關係為& 關係。而view:aaaa,表示使用者只要具有view許可權下的aaaa許可權即可訪問該方法或者類,而如果使用者具有view:aaaa許可權,而不具有view許可權,那麼當value={"view"}時是無法訪問的。當用戶具有view許可權時,則可以訪問所有的view許可權下的方法或者類。
“:”分號,設定許可權為子許可權與父許可權之間的關係,此時的許可權view即是父許可權,而view:aaaa為子許可權。
自動的頁面攔截:
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物件,然後呼叫subject的login方法來進行登入。(程式碼來自類: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填寫當值為空時的預設值
如果該部落格中存在錯誤,請在評論處指出。