SpringBoot 整合 Shiro(完整版)(1),java 企业级应用教程视频
之前写项目安全控件基本都是用的 SpringSecurity,后来发现 Shiro 在实际开发中用的也挺多的。这里一起来看一下 Shiro 在 Springboot 中的基本用法吧!想了解 SpringSecurity 的请移步:SpringSecurity安全控件使用指南。
1、简介
Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。相对于 SpringSecurity 简单的多,也没有 SpringSecurity 那么复杂。
2、shiro 架构
Shiro 三大功能模块
Subject:主体,一般指用户(把操作交给 SecurityManager)。
SecurityManager:安全管理器,管理所有 Subject,可以配合内部安全组件(关联 Realm)。
Realms:用于进行权限信息的验证,shiro 链接数据的桥梁。
细分功能
Authentication:身份认证/登录,验证用户是否拥有相应的身份(账号密码验证)。
Authorization:授权,验证某个已认证的用户是否拥有某权限。
Session Manager:会话管理,用户登录后,用户信息保存在 session 会话中。
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
Web Support:Web 支持,集成 Web 环境。
Caching:缓存,用户信息、角色、权限等缓存到如 redis 等缓存中。
Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。
Testing:测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
Remember Me:记住我,登录后,下次再来的话不用登录了。
1、表关系
菜单(TbMenu)=====> 页面上需要显示的所有菜单
角色(SysRole)=====> 角色及角色对应的菜单
用户(SysUser)=====> 用户及用户对应的角色
用户和角色中间表(sys_user_role)====> 用户和角色中间表
【注】 权限管理实现原理都差不多,虽然使用的安全控件不一样,但数据库表的设计基本一样,这里数据库表的设计和SpringSecurity实现动态权限菜单控制基本一致。
2、数据库表结构
菜单表 tb_menu
角色及菜单权限表 sys_role,其中父节点 parent 为 null 时为角色,不为 null 时为对应角色的菜单权限。
用户表 sys_user。
用户和角色多对多关系,用户和角色中间表 sys_user_role(由 Spring-Data-Jpa 自动生成)。
1、新建 Springboot 项目
创建一个 SprintBoot 项目,添加项目需要的依赖(这里持久层使用的是 SpringDataJpa)。
Shiro 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
pom.xml 完整依赖代码
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<groupId>com.mcy</groupId>
<artifactId>springboot-shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、项目结构
3、配置链接数据库属性
spring.datasource.url=jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.hibernate.use-new-id-generator-mappings=false
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
server.port=80
server.servlet.context-path=/shiro
1、编写实体类
菜单表实体类 TbMenu,Spring-Data-Jpa 可以根据实体类去数据库新建或更新对应的表结构,详情可以访问Spring-Data-Jpa入门
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
菜单表
@author
*/
@Entity
public class TbMenu {
private Integer id;
private String name;
private String url;
private Integer idx;
@JsonIgnore
private TbMenu parent;
@JsonIgnore
private List<TbMenu> children = new ArrayList<>();
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(unique = true)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
@ManyToOne
@CreatedBy
public TbMenu getParent() {
return parent;
}
public void setParent(TbMenu parent) {
this.parent = parent;
}
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
@OrderBy(value = "idx")
public List<TbMenu> getChildren() {
return children;
}
public void setChildren(List<TbMenu> children) {
this.children = children;
}
}
角色及权限表 SysRole,parent 为 null 时为角色,不为 null 时为权限。
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.data.annotation.CreatedBy;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
@Entity
/***
角色及角色对应的菜单权限
@author
*parent 为 null 时为角色,不为 null 时为权限
*/
public class SysRole {
private Integer id;
private String name; //名称
@JsonIgnore
private SysRole parent;
private Integer idx; //排序
@JsonIgnore
private List<SysRole> children = new ArrayList<>();
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToOne
@CreatedBy
public SysRole getParent() {
return parent;
}
public void setParent(SysRole parent) {
this.parent = parent;
}
@OneToMany(cascade = CascadeType.ALL, mappedBy = "parent")
public List<SysRole> getChildren() {
return children;
}
public void setChildren(List<SysRole> children) {
this.children = children;
}
public Integer getIdx() {
return idx;
}
public void setIdx(Integer idx) {
this.idx = idx;
}
}
最后实现的就是用户管理了,只需要对添加的用户分配对应的角色就可以了,用户登录时,显示角色对应的权限。
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;
/**
用户表
*/
@Entity
public class SysUser {
private Integer id;
private String username; //账号
private String password; //密码
private String name; //姓名
@JsonIgnore
private List<SysRole> roles = new ArrayList<>(); //角色
@Id
@GeneratedValue
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(length = 20, unique = true)
public String getUsername() {
return username;
}
public
void setUsername(String username) {
this.username = username;
}
@Column(length = 100)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Column(length = 20)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@ManyToMany(cascade = CascadeType.REFRESH, fetch = FetchType.EAGER)
@JoinTable(name = "sys_user_role", joinColumns = @JoinColumn(name = "user_id"), inverseJoinColumns = @JoinColumn(name = "role_id"))
public List<SysRole> getRoles() {
return roles;
}
public void setRoles(List<SysRole> roles) {
this.roles = roles;
}
}
2、Shiro 配置类
Shiro 配置类 ShiroConfig。
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
/**
@description Shiro 配置类
*/
@Configuration
public class ShiroConfig {
@Bean("hashedCredentialsMatcher")
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//指定加密方式为 MD5
credentialsMatcher.setHashAlgorithmName("MD5");
//加密次数
credentialsMatcher.setHashIterations(1024);
credentialsMatcher.setStoredCredentialsHexEncoded(true);
return credentialsMatcher;
}
@Bean("userRealm")
public UserRealm userRealm(@Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(matcher);
return userRealm;
}
@Bean
public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager")DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
// 设置 SecurityManager,安全管理器
bean.setSecurityManager(securityManager);
// 设置登录跳转页面
bean.setLoginUrl("/index");
// 设置未授权提示页面(没有权限访问后的页面)
bean.setUnauthorizedUrl("/unAuth");
/**
Shiro 内置过滤器,可以实现拦截器相关的拦截器
常用的过滤器:
**/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/login","anon");
filterMap.put("/user","authc");
filterMap.put("/system","perms[system]");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filterMap.put("/logout", "logout");
bean.setFilterChainDefinitionMap(filterMap);
return bean;
}
/**
注入 securityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(HashedCredentialsMatcher hashedCredentialsMatcher) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 关联 realm.
securityManager.setRealm(userRealm(hashedCredentialsMatcher));
return securityManager;
}
}
自定义 Realm 用于查询用户的角色和权限信息并保存到权限管理器中。代码如下:
import com.mcy.springbootshiro.entity.SysRole;
import com.mcy.springbootshiro.entity.SysUser;
import com.mcy.springbootshiro.service.SysUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
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.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserService userService;
/**
授权逻辑方法
**/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权");
//获取当前登录对象
Subject subject = SecurityUtils.getSubject();
SysUser user = (SysUser)subject.getPrincipal();
if(user != null){
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 角色与权限字符串集合
Collection<String> rolesCollection = new HashSet<>();
//获得当前用户的角色
List<SysRole> roles = user.getRoles();
for(SysRole role : roles){
rolesCollection.add(role.getName());
}
//添加当前用户的角色权限,用于判断可以访问那些功能
info.addStringPermissions(rolesCollection);
return info;
}
return null;
}
/**
认证
**/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证");
// 编写 shiro 判断逻辑,判断用户名和密码
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
// 判断用户名
SysUser bean = userService.findByUsername(token.getUsername());
//用户名不存在
if(bean == null){
throw new UnknownAccountException();
}
//以账号作为加密的盐值
ByteSource credentialsSalt = ByteSource.Util.bytes(bean.getUsername());
// 判断密码,
return new SimpleAuthenticationInfo(bean, bean.getPassword(),
credentialsSalt, getName());
}
}
其中密码加密使用的是 MD5 加密,加密代码如下:
public static void main(String[] args){
String hashAlgorithName = "MD5";
//加密密码
String password = "123456";
//加密次数
int hashIterations = 1024;
//账号作为加密的盐值
ByteSource credentialsSalt = ByteSource.Util.bytes("admin");
Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
评论