【SpringBoot】23、SpringBoot中整合Shiro实现权限管理

【SpringBoot】23、SpringBoot中整合Shiro实现权限管理之前在 SSM 项目中使用过 shiro 发现 shiro 的权限管理做的真不错 但是在 SSM 项目中的配置太繁杂了 于是这次在 SpringBoot 中使用了 shiro 下面一起看看吧一 简介 ApacheShiro 是一个强大且易用的 Java 安全框架 执行身份验证 授权 密码和会话管理 使用 Shiro 的易于理解的 API 您可以快速 轻松地获得任何应用程序 从最小的移动应用程序到最大的网络和企业应用程序 三个核心组件 1 Subject 即 当前操作用户 但是 在 Shiro 中 Subje

之前在 SSM 项目中使用过 shiro,发现 shiro 的权限管理做的真不错,但是在 SSM 项目中的配置太繁杂了,于是这次在 SpringBoot 中使用了 shiro,下面一起看看吧

一、简介

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

三个核心组件:

1、Subject

即“当前操作用户”。但是,在 Shiro 中,Subject 这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。Subject 代表了当前用户的安全操作,SecurityManager 则管理所有用户的安全操作。

2、SecurityManager

它是Shiro 框架的核心,典型的 Facade 模式,Shiro 通过 SecurityManager 来管理内部组件实例,并通过它来提供安全管理的各种服务。

3、Realm

Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro。当配置 Shiro 时,你必须至少指定一个 Realm,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。

二、整合 shiro

1、引入 maven 依赖

<!-- web支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- thymeleaf 模板引擎 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Shiro 权限管理 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.4</version> </dependency> <!-- 为了能够在 html 中使用 shiro 的标签引入 --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> 

我使用的 SpringBoot 版本是 2.3.1,其它依赖自己看着引入吧

2、创建 shiro 配置文件

关于 shiro 的配置信息,我们都放在 ShiroConfig.java 文件中

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; 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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; / * shiro配置类 */ @Configuration public class ShiroConfig { 
    / * 注入这个是是为了在thymeleaf中使用shiro的自定义tag。 */ @Bean(name = "shiroDialect") public ShiroDialect shiroDialect() { 
    return new ShiroDialect(); } / * 地址过滤器 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { 
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 设置登录url shiroFilterFactoryBean.setLoginUrl("/login"); // 设置主页url shiroFilterFactoryBean.setSuccessUrl("/"); // 设置未授权的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 开放登录接口 filterChainDefinitionMap.put("/doLogin", "anon"); // 开放静态资源文件 filterChainDefinitionMap.put("/css/", "anon"); filterChainDefinitionMap.put("/img/", "anon"); filterChainDefinitionMap.put("/js/", "anon"); filterChainDefinitionMap.put("/layui/", "anon"); // 其余url全部拦截,必须放在最后 filterChainDefinitionMap.put("/", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } / * 自定义安全管理策略 */ @Bean public SecurityManager securityManager() { 
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); / 设置自定义的relam */ securityManager.setRealm(loginRelam()); return securityManager; } / * 登录验证 */ @Bean public LoginRelam loginRelam() { 
    return new LoginRelam(); } / * 以下是为了能够使用@RequiresPermission()等标签 */ @Bean @DependsOn({ 
   "lifecycleBeanPostProcessor"}) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { 
    DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { 
    return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { 
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } } 

上面开放静态资源文件,其它博客说的是

filterChainDefinitionMap.put("/static/", "anon"); 

但我发现,我们在 html 文件中引入静态文件时,请求路径根本没有经过 static,thymeleaf 自动默认配置

static/ 

目录下面就是静态资源文件,所以,我们开放静态资源文件需要指定响应的目录路径

2、登录验证管理

关于登录验证的一些逻辑,以及赋权等操作,我们都放在 LoginRelam.java 文件中

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.RolePermissionService; import com.zyxx.sbm.service.UserInfoService; import com.zyxx.sbm.service.UserRoleService; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.CredentialsMatcher; 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.springframework.beans.factory.annotation.Autowired; import java.util.Set; / * 登录授权 */ public class LoginRelam extends AuthorizingRealm { 
    @Autowired private UserInfoService userInfoService; @Autowired private UserRoleService userRoleService; @Autowired private RolePermissionService rolePermissionService; / * 身份认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 
    // 获取基于用户名和密码的令牌:实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //根据用户名查找到用户信息 QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.eq("account", token.getUsername()); UserInfo userInfo = userInfoService.getOne(queryWrapper); // 没找到帐号 if (null == userInfo) { 
    throw new UnknownAccountException(); } // 校验用户状态 if ("1".equals(userInfo.getStatus())) { 
    throw new DisabledAccountException(); } // 认证缓存信息 return new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getAccount()), getName()); } / * 角色授权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 
    UserInfo authorizingUser = (UserInfo) principalCollection.getPrimaryPrincipal(); if (null != authorizingUser) { 
    //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //获得用户角色列表 Set<String> roleSigns = userRoleService.listUserRoleByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addRoles(roleSigns); //获得权限列表 Set<String> permissionSigns = rolePermissionService.listRolePermissionByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addStringPermissions(permissionSigns); return simpleAuthorizationInfo; } return null; } / * 自定义加密规则 * * @param credentialsMatcher */ @Override public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { 
    // 自定义认证加密方式 CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher(); // 设置自定义认证加密方式 super.setCredentialsMatcher(customCredentialsMatcher); } } 

以上就是登录时,需要指明 shiro 对用户的一些验证、授权等操作,还有自定义密码验证规则,在第3步会讲到,获取角色列表,权限列表,需要获取到角色与权限的标识,每一个角色,每一个权限都有唯一的标识,装入 Set 中

3、自定义密码验证规则

密码的验证规则,我们放在了 CustomCredentialsMatcher.java 文件中

import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import org.apache.shiro.crypto.hash.SimpleHash; / * @ClassName CustomCredentialsMatcher * 自定义密码加密规则 * @Author Lizhou * @Date 2020-07-10 16:24:24 / public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { 
    @Override public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) { 
    UsernamePasswordToken token = (UsernamePasswordToken) authcToken; //加密类型,密码,盐值,迭代次数 Object tokenCredentials = new SimpleHash("md5", token.getPassword(), token.getUsername(), 6).toHex(); // 数据库存储密码 Object accountCredentials = getCredentials(info); // 将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false return equals(tokenCredentials, accountCredentials); } } 

我们采用的密码加密方式为 MD5 加密,加密 6 次,使用登录账户作为加密密码的盐进行加密

4、密码加密工具

上面我们自定义了密码加密规则,我们创建一个密码加密的工具类 PasswordUtils.java 文件

import org.apache.shiro.crypto.hash.Md5Hash; / * 密码加密的处理工具类 */ public class PasswordUtils { 
    / * 迭代次数 */ private static final int ITERATIONS = 6; private PasswordUtils() { 
    throw new AssertionError(); } / * 字符串加密函数MD5实现 * * @param password 密码 * @param loginName 用户名 * @return */ public static String getPassword(String password, String loginName) { 
    return new Md5Hash(password, loginName, ITERATIONS).toString(); } } 

三、开始登录

上面,我们已经配置了 shiro 的一系列操作,从登录验证、密码验证规则、用户授权等等,下面我们就开始登录,登录的操作,放在了 LoginController.java 文件中

import com.zyxx.common.consts.SystemConst; import com.zyxx.common.enums.StatusEnums; import com.zyxx.common.kaptcha.KaptchaUtil; import com.zyxx.common.shiro.SingletonLoginUtils; import com.zyxx.common.utils.PasswordUtils; import com.zyxx.common.utils.ResponseResult; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.PermissionInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; / * @ClassName LoginController * @Description * @Author Lizhou * @Date 2020-07-02 10:54:54 / @Api(tags = "后台管理端--登录") @Controller public class LoginController { 
    @Autowired private PermissionInfoService permissionInfoService; @ApiOperation(value = "请求登录页面", notes = "请求登录页面") @GetMapping("login") public String init() { 
    return "login"; } @ApiOperation(value = "请求主页面", notes = "请求主页面") @GetMapping("/") public String index() { 
    return "index"; } @ApiOperation(value = "登录验证", notes = "登录验证") @ApiImplicitParams({ 
    @ApiImplicitParam(name = "account", value = "账号", required = true), @ApiImplicitParam(name = "password", value = "密码", required = true), @ApiImplicitParam(name = "resCode", value = "验证码", required = true), @ApiImplicitParam(name = "rememberMe", value = "记住登录", required = true) }) @PostMapping("doLogin") @ResponseBody public ResponseResult doLogin(String account, String password, String resCode, Boolean rememberMe, HttpServletRequest request, HttpServletResponse response) throws Exception { 
    // 验证码 if (!KaptchaUtil.validate(resCode, request)) { 
    return ResponseResult.getInstance().error(StatusEnums.KAPTCH_ERROR); } // 验证帐号和密码 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(account, password); // 记住登录状态 token.setRememberMe(rememberMe); try { 
    // 执行登录 subject.login(token); // 将用户保存到session中 UserInfo userInfo = (UserInfo) subject.getPrincipal(); request.getSession().setAttribute(SystemConst.SYSTEM_USER_SESSION, userInfo); return ResponseResult.getInstance().success(); } catch (UnknownAccountException e) { 
    return ResponseResult.getInstance().error("账户不存在"); } catch (DisabledAccountException e) { 
    return ResponseResult.getInstance().error("账户已被冻结"); } catch (IncorrectCredentialsException e) { 
    return ResponseResult.getInstance().error("密码不正确"); } catch (ExcessiveAttemptsException e) { 
    return ResponseResult.getInstance().error("密码连续输入错误超过5次,锁定半小时"); } catch (RuntimeException e) { 
    return ResponseResult.getInstance().error("未知错误"); } } @ApiOperation(value = "登录成功,跳转主页面", notes = "登录成功,跳转主页面") @PostMapping("success") public String success() { 
    return "redirect:/"; } @ApiOperation(value = "初始化菜单数据", notes = "初始化菜单数据") @GetMapping("initMenu") @ResponseBody public String initMenu() { 
    return permissionInfoService.initMenu(); } @ApiOperation(value = "退出登录", notes = "退出登录") @GetMapping(value = "loginOut") public String logout() { 
    Subject subject = SecurityUtils.getSubject(); subject.logout(); return "login"; } } 

当执行 subject.login(token); 时,就会进入我们在 第二步中第二条登录验证中,对用户密码、状态进行检查,对用户授权等操作,登录的密码,一定是通过密码加密工具得到的,不然验证不通过

四、页面权限控制

我们本次使用的是 thymeleaf 模板引擎,我们需要在 html 文件中加入以下内容

<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> 

引入了 thymeleaf 的依赖,以及 shiro 的依赖,这样我们就能在 html 文件中使用 thymeleaf、shiro 的标签了

例如:

1、判断当前用户有无此权限,通过权限标识

<button class="layui-btn" shiro:hasPermission="user_info_add"><i class="layui-icon">&#xe654;</i> 新增 </button> 

2、与上面相反,判断当前用户无此权限,通过权限标识,没有时验证通过

<button class="layui-btn" shiro:lacksPermission="user_info_add"><i class="layui-icon">&#xe654;</i> 新增 </button> 

3、判断当前用户有无以下全部权限,通过权限标识

<button class="layui-btn" shiro:hasAllPermissions="user_info_add"><i class="layui-icon">&#xe654;</i> 新增 </button> 

4、判断当前用户有无以下任一权限,通过权限标识

<button class="layui-btn" shiro:hasAnyPermissions="user_info_add"><i class="layui-icon">&#xe654;</i> 新增 </button> 

5、判断当前用户有无此角色,通过角色标识

<a shiro:hasRole="admin" href="admin.html">Administer the system</a> 

6、与上面相反,判断当前用户无此角色,通过角色标识,没有时验证通过

<a shiro:lacksRole="admin" href="admin.html">Administer the system</a> 

7、判断当前用户有无以下全部角色,通过角色标识

<a shiro:hasAllRoles="admin,role1,role2" href="admin.html">Administer the system</a> 

8、判断当前用户有无以下任一角色,通过角色标识

<a shiro:hasAnyRoles="admin,role1,role2" href="admin.html">Administer the system</a> 

以上,就是 SpringBoot 中整合 Shiro 实现权限管理的全部内容,希望能够帮助到正在阅读此博客的你

如您在阅读中发现不足,欢迎留言!!!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/208120.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月19日 下午12:18
下一篇 2026年3月19日 下午12:19


相关推荐

  • 全排列算法总结

    全排列算法总结本文同时发布在我的个人博客 https wiki hushhw cn posts 83505976 html 全排列递归算法算法思想求 n 位的字符串的全排列 先确定第 0 位 然后对后面 n 1 位进行全排列 在对 n 1 为进行全排列时 先确定第 1 位 然后对后面的 n 2 位进行全排列 由此得到递归函数和递归的结束条件 全排列也就是交换位置 到 n 2 位时 就是将

    2026年1月22日
    1
  • BlueZ_bluebonnet

    BlueZ_bluebonnet一、BlueZ在ubuntuPC上的基础应用1、bluez的安装及基本功能dong@ubuntu:~/bluez$lsbluez-5.47.tar.xzSPP-loopback.pydong@ubuntu:~/bluez$tarxvfbluez-5.47.tar.xzdong@ubuntu:~/bluez/bluez-5.47$./configure–pr…

    2025年8月21日
    6
  • 深入浅出Python——Python基础语法全解

    深入浅出Python——Python基础语法全解前言:Python是一个高层次的结合了解释性、编译性、互动性和面向对象的脚本语言。文章目录一、Python简介1.了解Python2.Python介绍3.Python特点4.Python发展历史5.Python版本二、Python解释器1.解释器的作用2.解释器的安装三、PyCharm安装与使用1.PyCharm的作用2.PyCharm安装与使用四、注释1.注释的作用2.注释的分类及语法五、变量1.变量的作用2.定义变量2.1标识符2.2命名习惯2.3使用变量2.4认识

    2022年6月24日
    24
  • JAVA笔试题_javabean面试题

    JAVA笔试题_javabean面试题JAVASE语法1.Java有没有goto语句?​ goto是Java中的保留字,在目前版本的Java中没有使用。根据JamesGosling(Java之父)编写的《TheJavaProgrammingLanguage》一书的附录中给出了一个Java关键字列表,其中有goto和const,但是这两个是目前无法使用的关键字,因此有些地方将其称之为保留字,其实保留字这个词应该有更广泛的意义,因为熟悉C语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单

    2025年9月22日
    9
  • Java 验证码识别(Tess4J初体验)「建议收藏」

    Java 验证码识别(Tess4J初体验)「建议收藏」Tess4J官方描述:AJavaJNAwrapperforTesseractOCRAPI.demo下载1.先去官网下载:http://tess4j.sourceforge.net/2.将下载的文件解压后把下面几个文件夹(图片中选中的)复制到新建的项目中3.将lib下的jar包加到buildpath中。注意:lib里面除了jar包还有别的。4.根据官网的样例在刚建的项目中使用一下:…

    2022年5月1日
    46
  • Vue(4)Vue指令的学习1「建议收藏」

    Vue(4)Vue指令的学习1「建议收藏」前言Vue官网一共有提供了14个指令,分别如下v-textv-htmlv-showv-if☆☆☆v-else☆☆☆v-else-if☆☆☆v-for☆☆☆v-on☆☆☆v

    2022年7月31日
    7

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号