Shiro 核心功能案例講解 基於SpringBoot 有源碼


Shiro 核心功能案例講解 基於SpringBoot 有源碼

從實戰中學習Shiro的用法。本章使用SpringBoot快速搭建項目。整合SiteMesh框架布局頁面。整合Shiro框架實現用身份認證,授權,數據加密功能。通過本章內容,你將學會用戶權限的分配規則,SpringBoot整合Shiro的配置,Shiro自定義Realm的創建,Shiro標簽式授權和注解式授權的使用場景,等實戰技能,還在等什么,快來學習吧!

技術:SpringBoot,Shiro,SiteMesh,Spring,SpringDataJpa,SpringMVC,Bootstrap-sb-admin-1.0.4
說明:前端使用的是Bootstrap-sb-admin模版。注意文章貼出的代碼可能不完整,請以github上源碼為主,謝謝!
源碼:https://github.com/ITDragonBlog/daydayup/tree/master/Shiro 喜歡的朋友可以鼓勵(star)下。
效果圖:

Shiro 功能介紹

四個核心:登錄認證,權限驗證,會話管理,數據加密。
六個支持:支持WEB開發,支持緩存,支持線程並發驗證,支持測試,支持用戶切換,支持"記住我"功能。

Authentication :身份認證,也可以理解為登錄,驗證用戶身份。
Authorization :權限驗證,也可以理解為授權,驗證用戶是否擁有某個權限;即判斷用戶是否能進行什么操作。
Session Manager :會話管理,用戶登錄后就是一次會話,在退出前,用戶的所有信息都在會話中。
Cryptography :數據加密,保護數據的安全性,常見的有密碼的加鹽加密。
Web Support :支持Web開發。
Caching :緩存,Shiro將用戶信息、擁有的角色/權限數據緩存,以提高程序效率。
Concurrency :支持多線程應用的並發驗證,即在一個線程中開啟另一個線程,Shiro能把權限自動傳播過去。
Testing :提供測試支持。
Run As :允許一個用戶以另一個用戶的身份進行訪問;前提是兩個用戶運行切換身份。
Remember Me :記住我,常見的功能,即登錄一次后,在指定時間內免登錄。

Shiro 功能介紹

Shiro 架構介紹

三個角色:當前用戶 Subject,安全管理器 SecurityManager,權限配置域 Realm。

Subject :代表當前用戶,提供了很多方法,如login和logout。Subject 只是一個門面,與Subject的所有交互都會委托給SecurityManager,SecurityManager才是真正的執行者;
SecurityManager :安全管理器;Shiro的核心,它負責與Shiro的其他組件進行交互,即所有與安全有關的操作都會與SecurityManager 交互;且管理着所有的 Subject;
Realm :Shiro 從 Realm 獲取安全數據(如用戶、角色、權限),SecurityManager 要驗證用戶身份,必需要從 Realm 獲取相應的用戶信息,判斷用戶身份是否合法,判斷用戶角色或權限是否授權。

SpringBoot 整合SiteMesh

SiteMesh 是一個網頁布局和修飾的框架,利用它可以將網頁的內容和頁面結構分離,以達到頁面結構共享的目的。

SiteMesh 統一了頁面的風格,減少了重復代碼,提高了頁面的復用率,是一款值得我們去學習的框架(也有很多坑)。當然,今天的主角是Shiro,這里只介紹它的基本用法。
SpringBoot 整合SiteMesh只需二個步驟:
第一步:配置攔截器FIlter,並在web中注冊bean。
第二步:創建裝飾頁面,引入常用的css和js文件,統一系統樣式。

配置攔截器FIlter

指定攔截的URL請求路徑,指定裝飾頁面的文件全路徑,指定不需要攔截的URL請求路徑。這里攔截所有請求到裝飾頁面,只有登錄頁面和靜態資源不攔截。

import org.sitemesh.builder.SiteMeshFilterBuilder;
import org.sitemesh.config.ConfigurableSiteMeshFilter;
/**
 * 配置SiteMesh攔截器FIlter,指定裝飾頁面和不需要攔截的路徑
 * @author itdragon
 */
public class WebSiteMeshFilter extends ConfigurableSiteMeshFilter{  
  
    @Override  
    protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {  
        builder.addDecoratorPath("/*", "/WEB-INF/layouts/default.jsp")  // 配置裝飾頁面
               .addExcludedPath("/static/*") 	// 靜態資源不攔截
               .addExcludedPath("/login**");  	// 登錄頁面不攔截
    }  

}
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * web.xml 配置
 * @author itdragon
 */
@Configuration
public class WebConfig {

	@Bean	// 配置siteMesh3
	public FilterRegistrationBean siteMeshFilter(){
		FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
		WebSiteMeshFilter siteMeshFilter = new WebSiteMeshFilter();
		filterRegistrationBean.setFilter(siteMeshFilter);
		return filterRegistrationBean;
	}
	
}

創建裝飾頁面

SiteMesh語法
<sitemesh:write property='title'/> : 被修飾頁面title的內容會在這里顯示。
<sitemesh:write property='head'/> : 被修飾頁面head的內容會在這里顯示,除了title。
<sitemesh:write property='body'/> : 被修飾頁面body的內容會在這里顯示。
需要注意的是:SiteMesh的jar有OpenSymphony(最新版是2009年)和Apache(最新版是2015年),兩者用法是有差異的。筆者選擇的是Apache版本的jar。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> 
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> 
<!DOCTYPE html>
<html>
	<head>
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
		<meta name="viewport" content="initial-scale=1.0, width=device-width, user-scalable=no" />
		<title>ITDragon系統-<sitemesh:write property='title'/></title>
		<link type="image/x-icon" href="images/favicon.ico" rel="shortcut icon">
		<c:set var="ctx" value="${pageContext.request.contextPath}" />
		<link href="${ctx}/static/sb-admin-1.0.4/css/bootstrap.min.css" rel="stylesheet">
	    <link href="${ctx}/static/sb-admin-1.0.4/css/sb-admin.css" rel="stylesheet">
		<sitemesh:write property='head'/>
	</head>
	<body>
		<div id="wrapper">
			<%@ include file="/WEB-INF/layouts/header.jsp"%>
			<div class='mainBody'>
		      <sitemesh:write property='body'/>
		    </div>
		</div>
	    <script src="${ctx}/static/sb-admin-1.0.4/js/jquery.js"></script>
	    <script src="${ctx}/static/sb-admin-1.0.4/js/bootstrap.min.js"></script>
	</body>
</html>

SpringBoot 整合Shiro

這是本章的核心知識點,SpringBoot 整合Shiro 有三個步驟:
第一步:創建實體類:用戶,角色,權限。確定三者關系,以方便Realm的授權工作。
第二步:創建自定義安全數據源Realm:負責用戶登錄認證,用戶操作授權。
第三步:創建Spring整合Shiro配置類:配置攔截規則,生命周期,安全管理器,安全數據源,等。

創建實體類

實體類:User,SysRole,SysPermission。
權限設計思路:
1). 角色表確定系統菜單資源,權限表確定菜單操作資源。
2). 用戶主要通過角色來獲取權限,且一個用戶可以擁有多個角色(不推薦,但必須支持該功能)。
3). 一個角色可以擁有多個權限,同時也可以有用多個用戶。
4). 一個權限可以被多個角色使用。
5). 工作都是從易到難,我們可以先從“一個用戶擁有一個角色,一個角色擁有多個權限”開始。
有了上面的分析,三個實體類代碼如下,省略了get/set方法。

import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
/**
 * 用戶實體類
 * @author itdragon
 */
@Table(name="itdragon_user_shiro")
@Entity
public class User {
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	private Long id;						// 自增長主鍵,默認ID為1的賬號為超級管理員
	private String account;					// 登錄的賬號
	private String userName;				// 注冊的昵稱
	@Transient
	private String plainPassword; 			// 登錄時的密碼,不持久化到數據庫
	private String password;				// 加密后的密碼
	private String salt;					// 用於加密的鹽
	private String iphone;					// 手機號
	private String email;					// 郵箱
	private String platform;				// 用戶來自的平台
	private String createdDate;				// 用戶注冊時間
	private String updatedDate;				// 用戶最后一次登錄時間
	@ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<SysRole> roleList;			// 一個用戶擁有多個角色
	private Integer status;					// 用戶狀態,0表示用戶已刪除
}
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
/**
 * 角色表,決定用戶可以訪問的頁面
 * @author itdragon
 */
@Table(name="itdragon_sysrole")
@Entity
public class SysRole {
    @Id
    @GeneratedValue
    private Integer id; 
    private String role; 		// 角色
    private String description; // 角色描述
    private Boolean available = Boolean.FALSE; // 默認不可用
    //角色 -- 權限關系:多對多關系; 取出這條數據時,把它關聯的數據也同時取出放入內存中
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<SysPermission> permissions;
    // 用戶 - 角色關系:多對多關系;
    @ManyToMany
    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})
    private List<User> users;
}
import java.util.List;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
/**
 * 權限表,決定用戶的具體操作
 * @author itdragon
 */
@Table(name = "itdragon_syspermission")
@Entity
public class SysPermission {
	@Id
	@GeneratedValue
	private Integer id;
	private String name; 		// 名稱
	private String url; 		// 資源路徑
	private String permission; 	// 權限字符串 如:employees:create,employees:update,employees:delete
	private Boolean available = Boolean.FALSE; // 默認不可用
	@ManyToMany
	@JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {@JoinColumn(name = "roleId") })
	private List<SysRole> roles;
}

創建自定義安全數據源Realm

Shiro 從 Realm 獲取安全數據(如用戶、角色、權限),SecurityManager 身份認證和權限認證都是從Realm中獲取相應的用戶信息,然后做比較判斷是否有身份登錄,是否有權限操作。
Shiro 支持多個Realm。同時也有不同的認證策略:
FirstSuccessfulStrategy : 只要有一個Realm成功就返回,后面的忽略;
AtLeastOneSuccessfulStrategy : 只要有一個Realm成功就通過,返回所有認證成功的信息,默認;
AllSuccessfulStrategy : 必須所有Realm都成功才算通過

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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import com.itdragon.pojo.SysPermission;
import com.itdragon.pojo.SysRole;
import com.itdragon.pojo.User;
import com.itdragon.service.UserService;

/**
 * 自定義安全數據Realm,重點
 * @author itdragon
 */
public class ITDragonShiroRealm extends AuthorizingRealm {
	
	private static final transient Logger log = LoggerFactory.getLogger(ITDragonShiroRealm.class);
	
	@Autowired
	private UserService userService;
	
	/**
	 * 授權
	 */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    	log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置當前用戶權限");
    	String username = (String) principals.getPrimaryPrincipal();
    	User user = userService.findByAccount(username);
    	if(null == user){
            return null;
        }
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		for (SysRole role : user.getRoleList()) {
			authorizationInfo.addRole(role.getRole());	// 添加角色
			for (SysPermission permission : role.getPermissions()) {
				authorizationInfo.addStringPermission(permission.getPermission());	// 添加具體權限
			}
		}
        return authorizationInfo;
    }

    /**
     * 身份認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
            throws AuthenticationException {
    	log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 認證用戶身份信息");
		String username = (String) token.getPrincipal(); // 獲取用戶登錄賬號
		User userInfo = userService.findByAccount(username); // 通過賬號查加密后的密碼和鹽,這里一般從緩存讀取
        if(null == userInfo){
            return null;
        }
		// 1). principal: 認證的實體信息. 可以是 username, 也可以是數據表對應的用戶的實體類對象. 
		Object principal = username;
		// 2). credentials: 加密后的密碼. 
		Object credentials = userInfo.getPassword();
		// 3). realmName: 當前 realm 對象的唯一名字. 調用父類的 getName() 方法
		String realmName = getName();
		// 4). credentialsSalt: 鹽值. 注意類型是ByteSource
		ByteSource credentialsSalt = ByteSource.Util.bytes(userInfo.getSalt());
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
    }
}

創建Spring整合Shiro配置類

第一步:配置Shiro攔截器,指定URL請求的權限。首先靜態資源和登錄請求匿名訪問,然后是用戶登出操作,最后是所有請求都需身份認證。Shiro攔截器優先級是從上到下,切勿將/**=authc,放在前面。
第二步:配置Shiro生命周期處理器,
第三步:配置自定義Realm,負責身份認證和授權。
第四步:配置安全管理器SecurityManager,Shiro的核心。

import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
/**
 * Shiro 配置,重點
 * @author itdragon
 */
@Configuration
public class ShiroSpringConfig {

	private static final transient Logger log = LoggerFactory.getLogger(ShiroSpringConfig.class);

	/**
	 * 配置攔截器
	 *  
	 * 定義攔截URL權限,優先級從上到下 
	 * 1). anon  : 匿名訪問,無需登錄 
	 * 2). authc : 登錄后才能訪問 
	 * 3). logout: 登出
	 * 4). roles : 角色過濾器
	 * 
	 * URL 匹配風格
	 * 1). ?:匹配一個字符,如 /admin? 將匹配 /admin1,但不匹配 /admin 或 /admin/;
	 * 2). *:匹配零個或多個字符串,如 /admin* 將匹配 /admin 或/admin123,但不匹配 /admin/1;
	 * 2). **:匹配路徑中的零個或多個路徑,如 /admin/** 將匹配 /admin/a 或 /admin/a/b
	 * 
	 * 配置身份驗證成功,失敗的跳轉路徑
	 */
	@Bean
	public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {
		log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置Shiro攔截工廠");
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		filterChainDefinitionMap.put("/static/**", "anon");	// 靜態資源匿名訪問
		filterChainDefinitionMap.put("/employees/login", "anon");// 登錄匿名訪問
		filterChainDefinitionMap.put("/logout", "logout");	// 用戶退出,只需配置logout即可實現該功能
		filterChainDefinitionMap.put("/**", "authc");		// 其他路徑均需要身份認證,一般位於最下面,優先級最低
		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		shiroFilterFactoryBean.setLoginUrl("/login");		// 登錄的路徑
		shiroFilterFactoryBean.setSuccessUrl("/dashboard");	// 登錄成功后跳轉的路徑
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");	// 驗證失敗后跳轉的路徑
		return shiroFilterFactoryBean;
	}
	
	/**
     * 配置Shiro生命周期處理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }
    
    /**
     * 自動創建代理類,若不添加,Shiro的注解可能不會生效。
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
    
    /**
     * 開啟Shiro的注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
	
    /**
     * 配置加密匹配,使用MD5的方式,進行1024次加密
     */
	@Bean
	public HashedCredentialsMatcher hashedCredentialsMatcher() {
		HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
		hashedCredentialsMatcher.setHashAlgorithmName("MD5");
		hashedCredentialsMatcher.setHashIterations(1024);
		return hashedCredentialsMatcher;
	}

	/**
	 * 自定義Realm,可以多個
	 */
	@Bean
	public ITDragonShiroRealm itDragonShiroRealm() {
		ITDragonShiroRealm itDragonShiroRealm = new ITDragonShiroRealm();
		itDragonShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
		return itDragonShiroRealm;
	}

	/**
	 * SecurityManager 安全管理器;Shiro的核心
	 */
	@Bean
	public DefaultWebSecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		securityManager.setRealm(itDragonShiroRealm());
		return securityManager;
	}

}

實現業務邏輯

系統有四個菜單:控制面板 Dashboard,員工管理 Employees,權限管理 Permissions,角色管理 Roles 。
系統有三個角色:超級管理員 admin, 經理 manager, 普通員工 staff 。
業務的邏輯要求:

  1. admin角色可以訪問所有菜單,manager角色除了Roles菜單外都可以訪問,staff角色只能訪問Dashboard和Employees菜單 。
  2. admin角色擁有刪除用戶信息的權限,其他兩個角色沒有權限。

實現業務邏輯步驟:
第一步:模擬數據,創建用戶,角色,權限數據。
第二步:左側菜單權限配置,需要用到Shiro的標簽式授權。
第三步:在刪除用戶的Controller層方法上配置操作權限,需要用到Shiro的注解式授權。
第四步:權限驗證失敗統一處理。

配置數據

sql文件路徑:https://github.com/ITDragonBlog/daydayup/tree/master/Shiro/springboot-shiro/sql
建議先執行sql文件,再啟動項目。
用戶密碼通常采用加鹽加密的方式,筆者采用MD5的加密方式,以UUID作為鹽,進行1024次加密。代碼如下:

import java.util.UUID;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import com.itdragon.pojo.User;
/**
 * 工具類
 * @author itdragon
 */
public class ItdragonUtils {
	
	private static final String ALGORITHM_NAME = "MD5";
	private static final Integer HASH_ITERATIONS = 1024;
	
	public static void entryptPassword(User user) {
		String salt = UUID.randomUUID().toString();
		String temPassword = user.getPlainPassword();
		Object md5Password = new SimpleHash(ALGORITHM_NAME, temPassword, ByteSource.Util.bytes(salt), HASH_ITERATIONS);
		user.setSalt(salt);
		user.setPassword(md5Password.toString());
	}

}

左側菜單權限配置

系統使用了SiteMesh框架,左側菜單頁面屬於修飾頁面的一部分。只需要在一個文件中添加shiro的標簽,就可以在整個系統生效,耦合性很低。
<shiro:guest> : 允許游客訪問的代碼塊
<shiro:user> : 允許已經驗證或者通過"記住我"登錄的用戶才能訪問的代碼塊。
<shiro:authenticated> : 只有通過登錄操作認證身份,而並非通過"記住我"登錄的用戶才能訪問的代碼塊。
<shiro:notAuthenticated> : 未登錄的用戶顯示的代碼塊。
<shiro:principal> : 顯示當前登錄的用戶信息。
<shiro:hasRole name="admin"> : 只有擁有admin角色的用戶才能訪問的代碼塊。
<shiro:hasAnyRoles name="admin,manager"> : 只有擁有admin或者manager角色的用戶才能訪問的代碼塊。
<shiro:lacksRole name="admin"> : 沒有admin角色的用戶顯示的代碼塊
<shiro:hasPermission name="admin:delete"> : 只有擁有"admin:delete"權限的用戶才能訪問的代碼塊。
<shiro:lacksPermission name="admin:delete"> : 沒有"admin:delete"權限的用戶顯示的代碼塊。

<div class="collapse navbar-collapse navbar-ex1-collapse">
	<ul class="nav navbar-nav side-nav itdragon-nav">
		<li class="active">
			<a href="/dashboard"><i class="fa fa-fw fa-dashboard"></i> Dashboard</a>
		</li>
		<li>
			<a href="/employees"><i class="fa fa-fw fa-bar-chart-o"></i> Employees</a>
		</li>
		<!-- 只有角色為admin或manager的用戶才有權限訪問  -->
		<shiro:hasAnyRoles name="admin,manager">
		<li>
			<a href="/permission"><i class="fa fa-fw fa-table"></i> Permissions</a>
		</li>
		</shiro:hasAnyRoles>
		<!-- 只有角色為admin的用戶才有權限訪問  -->
		<shiro:hasRole name="admin">
		<li>
			<a href="/roles"><i class="fa fa-fw fa-file"></i> Roles</a>
		</li>
		</shiro:hasRole>
	</ul>
</div>

在操作上添加權限

Shiro常見的權限注解有:
@RequiresAuthentication : 表示當前 Subject 已經認證登錄的用戶才能調用的代碼塊。
@RequiresUser : 表示當前 Subject 已經身份驗證或通過記住我登錄的。
@RequiresGuest : 表示當前 Subject 沒有身份驗證,即是游客身份。
@RequiresRoles(value={"admin", "user"}, logical=Logical.AND) : 表示當前 Subject 需要角色 admin和user
@RequiresPermissions (value={"user:update", "user:delete"}, logical= Logical.OR) : 表示當前 Subject 需要權限 user:update或user:delete。
這里值得注意的是:如果你的注解沒有生效,很可能沒有配置Shiro注解開啟的問題。

@RequestMapping(value = "delete/{id}")
@RequiresPermissions(value={"employees:delete"})
public String delete(@PathVariable("id") Long id, RedirectAttributes redirectAttributes) {
	userService.deleteUser(id);
	redirectAttributes.addFlashAttribute("message", "刪除用戶成功");
	return "redirect:/employees";
}

權限驗證失敗統一處理

Shiro提供權限驗證失敗跳轉頁面的功能,但這個邏輯是不友好的。我們需要統一處理權限驗證失敗,並返回執行失敗的頁面。

import org.apache.shiro.web.util.WebUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
 * 異常統一處理
 * @author itdragon
 */
@ControllerAdvice
public class ExceptionController {
	
	@ExceptionHandler(org.apache.shiro.authz.AuthorizationException.class)
    public String handleException(RedirectAttributes redirectAttributes, Exception exception, HttpServletRequest request) {
        redirectAttributes.addFlashAttribute("message", "抱歉!您沒有權限執行這個操作,請聯系管理員!");
		String url = WebUtils.getRequestUri(request);
		return "redirect:/" + url.split("/")[1];	// 請求的規則 : /page/operate
    }
	
}

Shiro和SpringSecurity

  1. Shiro使用更簡單,更容易上手。
  2. Spring Security功能更強大,和Spring無縫整合,但學習門檻比Shiro高。
  3. 我的建議是兩個都可以學習,誰知道公司下一秒會選擇什么框架。。。

總結

  1. Shiro 四個核心功能:身份認證,授權,數據加密,Seesion管理。
  2. Shiro 三個重要角色:Subject,SecurityManager,Realm。
  3. Shiro 五個常見開發:自定義Realm,配置攔截器,標簽式授權控制菜單,注解式授權控制操作,權限不夠異常統一處理。
  4. 項目搭建推薦從攔截器開始,然后再是身份認證,角色權限認證,操作權限認證。
  5. Shiro 其他知識后續介紹。

到這里Shiro 核心功能案例講解 基於SpringBoot 的文章就寫完了,一個基本的系統也搭完了。還有很多缺陷和建議,不吝賜教!如果文章對你有幫助,可以點個"推薦",也可以"關注"我,獲得更多豐富的知識。

其他知識查考文獻

Shiro 權限注解 :http://blog.csdn.net/w_stronger/article/details/73109248

Spring @ControllerAdvice注解 : http://blog.csdn.net/jackfrued/article/details/76710885

bootstrap 模塊頁面 : https://startbootstrap.com/template-categories/all/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM