Spring Boot入門教程(7)---整合jpa,Shiro進行權限管理(附源碼)

前面已經介紹過springBoot和mybatis、JPA的整合,本篇主要是在上一篇的基礎上整合Shiro進行權限的管理。java

源碼連接mysql

一、簡介

先簡單介紹下shiro吧,其實它就是一個安全框架,相比spring Security使用起來更簡單易懂。引用一張架構圖(圖片來自官網),從圖中你們能夠看到他的三大核心:git

 -Subject 當前用戶操做 
- SecurityManager 用於管理全部的Subject 
- Realms 用於進行權限信息的驗證,也是咱們須要本身實現的。web

咱們須要實現Realms的Authentication 和 Authorization。其中 Authentication 是用來驗證用戶身份,Authorization 是受權訪問控制,用於對用戶進行的操做受權,證實該用戶是否容許進行當前操做,如訪問某個連接,某個資源文件等。
Apache Shiro 核心經過 Filter 來實現,就好像SpringMvc 經過DispachServlet 來主控制同樣。 
既然是使用 Filter 通常也就能猜到,是經過URL規則來進行過濾和權限校驗,因此咱們須要定義一系列關於URL的規則和訪問權限。
另外咱們能夠經過Shiro 提供的會話管理來獲取Session中的信息。Shiro 也提供了緩存支持,使用 CacheManager 來管理。spring

要集成shiro咱們必須知道他的幾個核心對象,分別是:
第一:ShiroFilterFactory,Shiro過濾器工廠類,具體的實現類是:ShiroFilterFactoryBean,此實現類是依賴於SecurityManager安全管理器。
第二:SecurityManager,Shiro的安全管理,主要是身份認證的管理,緩存管理,cookie管理,因此在實際開發中咱們主要是和SecurityManager進行打交道的,ShiroFilterFactory主要配置好了Filter就能夠了。固然SecurityManager並進行身份認證緩存的實現,咱們須要進行對應的編碼而後進行注入到安全管理器中。
第三:Realm,用於身份信息權限信息的驗證。
第四:其它的就是緩存管理,記住登陸之類的,這些大部分都是須要本身進行簡單的實現,而後注入到SecurityManager讓Shiro的安全管理器進行管理就行了。sql

二、整合完成demo

概念介紹完了 ,咱們開始動手,完成咱們的demo,具體步驟以下:數據庫

(a) pom.xml中添加Shiro依賴;
(b) 注入Shiro Factory和SecurityManager。
(c) 身份認證
(d) 權限控制apache

(a) 添加Shiro依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.pxk</groupId>
	<artifactId>SpringBootDemo_JPA</artifactId>
	<packaging>war</packaging>
	<version>0.0.1-SNAPSHOT</version>
	<name>SpringBootDemo_JPA Maven Webapp</name>
	<url>http://maven.apache.org</url>
	<build>
		<finalName>SpringBootDemo_JPA</finalName>
	</build>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.6.RELEASE</version>
		<relativePath />
	</parent>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<!-- web容器 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
			<version>1.5.6.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>4.3.10.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.40</version>
		</dependency>
		<!--日誌 -->
		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.17</version>
		</dependency>
		<!-- druid鏈接池 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.18</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- shiro -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.2.3</version>
		</dependency>
	</dependencies>
</project>

(b) 注入Shiro Factory和SecurityManager

    使用springBoot的配置方式,新建config類,主要配置兩個類ShiroFilterFactory和SecurityManager緩存

package com.pxk.springboot.config;

import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfiguration {

	@Bean
	public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
		ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
		// 必須設置SecuritManager
		shiroFilterFactoryBean.setSecurityManager(securityManager);
		// 攔截器
		Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
		// 配置退出過濾器,其中的具體代碼Shiro已經替咱們實現了
		filterChainDefinitionMap.put("/logout", "logout");
		// <!-- 過濾鏈定義,從上向下順序執行,通常將 /**放在最爲下邊 -->:這是一個坑呢,一不當心代碼就很差使了;
		// <!-- authc:全部url都必須認證經過才能夠訪問; anon:全部url都均可以匿名訪問-->
		filterChainDefinitionMap.put("/**", "authc");

		// 若是不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
		shiroFilterFactoryBean.setLoginUrl("/login");
		// 登陸成功後要跳轉的連接
		shiroFilterFactoryBean.setSuccessUrl("/index");
		// 未受權界面;
		shiroFilterFactoryBean.setUnauthorizedUrl("/403");

		shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
		return shiroFilterFactoryBean;

	}

	@Bean
	public SecurityManager securityManager() {
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		return securityManager;
	}

}

(c) 身份認證

     在認證、受權內部實現機制中都有提到,最終處理都將交給Real進行處理。由於在Shiro中,最終是經過Realm來獲取應用程序中的用戶、角色及權限信息的。一般狀況下,在Realm中會直接從咱們的數據源中獲取Shiro須要的驗證信息。能夠說,Realm是專用於安全框架的DAO.
認證明現
Shiro的認證過程最終會交由Realm執行,這時會調用Realm的getAuthenticationInfo(token)方法。
該方法主要執行如下操做:
一、檢查提交的進行認證的令牌信息
二、根據令牌信息從數據源(一般爲數據庫)中獲取用戶信息
三、對用戶信息進行匹配驗證。
四、驗證經過將返回一個封裝了用戶信息的AuthenticationInfo實例。
五、驗證失敗則拋出AuthenticationException異常信息。
而在咱們的應用程序中要作的就是自定義一個Realm類,繼承AuthorizingRealm抽象類,重載doGetAuthenticationInfo (),重寫獲取用戶信息的方法。
既然須要進行身份權限控制,那麼少不了建立用戶實體類,權限實體類。
      在權限管理系統中,有這麼幾個角色很重要,這個要是不清楚的話,那麼就很難理解,咱們爲何這麼編碼了。第一是用戶表:在用戶表中保存了用戶的基本信息,帳號、密碼、姓名,性別等;第二是:權限表(資源+控制權限):這個表中主要是保存了用戶的URL地址,權限信息;第三就是角色表:在這個表重要保存了系統存在的角色;第四就是關聯表:用戶-角色管理表(用戶在系統中都有什麼角色,好比admin,vip等),角色-權限關聯表(每一個角色都有什麼權限能夠進行操做)。依據這個理論,咱們進行來進行編碼,很明顯的咱們第一步就是要進行實體類的建立。在這裏咱們使用Mysql和JPA進行操做數據庫。安全

新建實體類UserInfo、SysRole、SysPermission(採用逆向工程生成數據庫表,而後導入數據)--非關鍵的get、set方法省略

UserInfo.java

package com.pxk.springboot.domain;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Column;
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;

/**
 * 用戶信息.
 * 
 * @author Administrator
 * 
 */
@Entity
public class UserInfo implements Serializable {

	/**  
	 *   
	 */
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue
	private long uid;// 用戶id

	@Column(unique = true)
	private String username;// 賬號

	private String name;// 名稱(暱稱或者真實姓名,不一樣系統不一樣定義)

	private String password; // 密碼;
	private String salt;// 加密密碼的鹽

	private byte state;// 用戶狀態,0:建立未認證(好比沒有激活,沒有輸入驗證碼等等)--等待驗證的用戶 ,
						// 1:正常狀態,2:用戶被鎖定.

	@ManyToMany(fetch = FetchType.EAGER) // 當即從數據庫中進行加載數據
	@JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns = {
			@JoinColumn(name = "roleId") })
	private List<SysRole> roleList;// 一個用戶具備多個角色

	/**
	 * 密碼鹽.
	 * 
	 * @return
	 */
	public String getCredentialsSalt() {
		return this.username + this.salt;
	}

	@Override
	public String toString() {
		return "UserInfo [uid=" + uid + ", username=" + username + ", name=" + name + ", password=" + password
				+ ", salt=" + salt + ", state=" + state + "]";
	}

}

SysRole.java

package com.pxk.springboot.domain;

import java.io.Serializable;
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;

/**
 * 系統角色實體類;
 * 
 * @author Administrator
 * 
 */
@Entity
public class SysRole implements Serializable {
	private static final long serialVersionUID = 1L;
	@Id
	@GeneratedValue
	private Long id; // 編號
	private String role; // 角色標識程序中判斷使用,如"admin",這個是惟一的:
	private String description; // 角色描述,UI界面顯示使用
	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<UserInfo> userInfos;// 一個角色對應多個用戶

	@Override
	public String toString() {
		return "SysRole [id=" + id + ", role=" + role + ", description=" + description + ", available=" + available
				+ ", permissions=" + permissions + "]";
	}
}

SysPermission.java

package com.pxk.springboot.domain;

import java.io.Serializable;
import java.util.List;

import javax.persistence.Column;
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;

/**
 * 權限實體類;
 * 
 */
@Entity
public class SysPermission implements Serializable {
	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue
	private long id;// 主鍵.
	private String name;// 名稱.

	@Column(columnDefinition = "enum('menu','button')")
	private String resourceType;// 資源類型,[menu|button]
	private String url;// 資源路徑.
	private String permission; // 權限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
	private Long parentId; // 父編號
	private String parentIds; // 父編號列表
	private Boolean available = Boolean.FALSE;

	@Override
	public String toString() {
		return "SysPermission [id=" + id + ", name=" + name + ", resourceType=" + resourceType + ", url=" + url
				+ ", permission=" + permission + ", parentId=" + parentId + ", parentIds=" + parentIds + ", available="
				+ available + "]";
	}

}

MyShiroRealm.java

該類是實現權限認證的核心,須要咱們手動實現

package com.pxk.springboot.config;

import javax.annotation.Resource;

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 com.pxk.springboot.domain.SysPermission;
import com.pxk.springboot.domain.SysRole;
import com.pxk.springboot.domain.UserInfo;
import com.pxk.springboot.serivce.UserInfoService;

/**
 * 身份校驗覈心類
 * 
 * @author Administrator
 * 
 */
public class MyShiroRealm extends AuthorizingRealm {

	@Resource
	private UserInfoService userInfoService;

	/**
	 * 認證信息(身份驗證) Authentication 是用來驗證用戶身份
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
		// 獲取用戶的輸入賬號
		String username = (String) token.getPrincipal();
		System.out.println(token.getCredentials());
		// 經過username從數據庫中查找 User對象,若是找到,沒找到.
		// 實際項目中,這裏能夠根據實際狀況作緩存,若是不作,Shiro本身也是有時間間隔機制,2分鐘內不會重複執行該方法
		UserInfo userInfo = userInfoService.findByUsername(username);
		System.out.println("----->>userInfo=" + userInfo);
		if (userInfo == null) {
			return null;
		}

		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用戶名
				userInfo.getPassword(), // 密碼
				ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt
				getName() // realm name
		);
		return authenticationInfo;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
		// TODO Auto-generated method stub
		System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");

		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
		UserInfo userInfo = (UserInfo) principals.getPrimaryPrincipal();

		for (SysRole role : userInfo.getRoleList()) {

			authorizationInfo.addRole(role.getRole());
			System.out.println(role.getPermissions());
			for (SysPermission p : role.getPermissions()) {
				System.out.println(p);
				authorizationInfo.addStringPermission(p.getPermission());
			}
		}
		return authorizationInfo;
	}

}

核心功能基本完成,接下來就是編寫controller和頁面了

頁面代碼我就不詳細貼出了,直接給你們源碼地址進行下載

相關文章
相關標籤/搜索