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和页面了

页面代码我就不详细贴出了,直接给你们源码地址进行下载

相关文章
相关标签/搜索