写点什么

SpringBoot 整合 Shiro(完整版)(1),java 企业级应用教程视频

作者:Java高工P7
  • 2021 年 11 月 10 日
  • 本文字数:6402 字

    阅读完需:约 21 分钟




之前写项目安全控件基本都是用的 SpringSecurity,后来发现 Shiro 在实际开发中用的也挺多的。这里一起来看一下 Shiro 在 Springboot 中的基本用法吧!想了解 SpringSecurity 的请移步:SpringSecurity安全控件使用指南


Shiro 简介



1、简介

Apache Shiro 是 Java 的一个安全(权限)框架。 Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。相对于 SpringSecurity 简单的多,也没有 SpringSecurity 那么复杂。

2、shiro 架构


Shiro 三大功能模块


  1. Subject:主体,一般指用户(把操作交给 SecurityManager)。

  2. SecurityManager:安全管理器,管理所有 Subject,可以配合内部安全组件(关联 Realm)。

  3. Realms:用于进行权限信息的验证,shiro 链接数据的桥梁。


细分功能


  1. Authentication:身份认证/登录,验证用户是否拥有相应的身份(账号密码验证)。

  2. Authorization:授权,验证某个已认证的用户是否拥有某权限。

  3. Session Manager:会话管理,用户登录后,用户信息保存在 session 会话中。

  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。

  5. Web Support:Web 支持,集成 Web 环境。

  6. Caching:缓存,用户信息、角色、权限等缓存到如 redis 等缓存中。

  7. Concurrency:多线程并发验证,在一个线程中开启另一个线程,可以把权限自动传播过去。

  8. Testing:测试支持;

  9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。

  10. 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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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);

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
SpringBoot整合Shiro(完整版)(1),java企业级应用教程视频