当前位置: 首页 > news >正文

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架

  • 一、 什么是Shiro
  • 二、Shiro 组件
    • 核心组件
    • 其他组件
  • 三、流程说明
    • shiro的运行流程
  • 四、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)
    • 1. 添加 Shiro 相关 maven
    • 2. 添加 其他 maven
    • 3. 设计数据库表
    • 4. 使用mybatisplus逆向工程生成代码
    • 5. 添加 自定义Realm 类
      • 扩展:授权类方法 对应的 注解
    • 6. 添加 ShiroConfig 配置类
      • 扩展:Shiro过滤机制表
    • 7. 开始测试—后端
      • 1. 创建一个LoginController 类
      • 2. 创建一个UserController 类
    • 8. 开始测试—前端
      • 1. 编写login.html页面
      • 2. 编写index.html页面
      • 3. 编写403页面
    • 10、测试遇到的问题
  • 五、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)
    • 1. 添加 maven 依赖
    • 2. 添加Shiro的配置
    • 3. 添加 ShiroConfig 配置类

一、 什么是Shiro

shiro是apache的一个开源框架,是一个权限管理的框架,实现 用户认证、用户授权。

spring中有spring security (原名Acegi),是一个权限框架,它和spring依赖过于紧密,没有shiro使用简单。

shiro不依赖于spring,shiro不仅可以实现 web应用的权限管理,还可以实现c/s系统,分布式系统权限管理,shiro属于轻量框架,越来越多企业项目开始使用shiro。

使用shiro实现系统的权限管理,有效提高开发效率,从而降低开发成本。

二、Shiro 组件

核心组件

主要有三大组件 为 Subject、SecurityManager、 Realms

组件名称概念作用
Subject主体可以是用户也可以是程序,主体要访问系统,系统需要对主体进行认证、授权
SecurityManager安全管理器主体进行认证和授权都是通过securityManager进行。
它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制。
Realm域,领域,相当于数据源开发者可自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现

其他组件

组件名称概念作用
Authenticator认证器主体进行认证最终通过authenticator进行的。
Authorizer授权器主体进行授权最终通过authorizer进行的。
SessionManagerweb应用中一般是用web容器对session进行管理,
所以shiro也提供一套session管理的方式。
SessionDao通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
Cache Manager缓存管理器主要对session和授权数据进行缓存。
比如将授权数据通过cacheManager进行缓存管理,和ehcache整合对缓存数据进行管理。
DefaultWebSecurityManager安全管理器开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
ShiroFilterFactoryBean过滤器工厂负责创建并配置过滤链(filter chain)。这些过滤器可以应用于HTTP请求以实现访问控制。

三、流程说明

shiro的运行流程

在这里插入图片描述

  • Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息;
  • 使用Subject主体获取到封装着用户的数据的标识token
  • Subject把标识token交给SecurityManager,在SecurityManager安全中心,SecurityManager把标识token委托给认证器Authenticator进行身份证。认证器的作用是一般用来指定如何验证,它规定本次认证用到那些Realm
  • 认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

四、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)

1. 添加 Shiro 相关 maven

        <!--shiro--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.12.0</version></dependency>
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-core</artifactId>-->
<!--            <version>1.12.0</version>-->
<!--        </dependency>-->
<!--        <dependency>-->
<!--            <groupId>org.apache.shiro</groupId>-->
<!--            <artifactId>shiro-web</artifactId>-->
<!--            <version>1.12.0</version>-->
<!--        </dependency>-->

2. 添加 其他 maven

       <!-- lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><!--provided 意味着打包的时候可以不用包进去--><scope>provided</scope></dependency><!-- MySQL JDBC驱动 --><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId></dependency><!--mybatis-plus 这个版本需要指定了,因为场景启动器里面没有 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.7</version></dependency><!-- MyBatis-Plus代码生成器(逆向工程)--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.7</version></dependency><!-- MyBatis-Plus代码生成器所需引擎模板引擎来生成代码文件 --><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId></dependency><!-- Thymeleaf 用于访问html文件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--在Thymeleaf中使用shiro标签--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency>

3. 设计数据库表

用户表、角色表、权限表、用户角色权限、角色权限表

-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user`  (`id` int NOT NULL AUTO_INCREMENT,`username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL,`password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL,`create_time` datetime NULL DEFAULT NULL,`status` int NULL DEFAULT NULL COMMENT '状态',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, '超级小明', '12345', NULL, NULL);
INSERT INTO `tb_user` VALUES (2, '王子', '12345', NULL, NULL);-- ----------------------------
-- Table structure for tb_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role`  (`id` int NOT NULL AUTO_INCREMENT,`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL COMMENT '角色名称',`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL COMMENT '描述',PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of tb_role
-- ----------------------------
INSERT INTO `tb_role` VALUES (1, '超级管理员', NULL);
INSERT INTO `tb_role` VALUES (2, '产品管理员', NULL);
INSERT INTO `tb_role` VALUES (3, '订单管理员', NULL);-- ----------------------------
-- Table structure for tb_permission
-- ----------------------------
DROP TABLE IF EXISTS `tb_permission`;
CREATE TABLE `tb_permission`  (`id` int NOT NULL AUTO_INCREMENT,`url` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL,`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_bin NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of tb_permission
-- ----------------------------
INSERT INTO `tb_permission` VALUES (1, '查询', 'user:queryAll');
INSERT INTO `tb_permission` VALUES (2, '增加', 'user:add');
INSERT INTO `tb_permission` VALUES (3, '删除', 'user:delete');-- ----------------------------
-- Table structure for tb_user_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_user_role`;
CREATE TABLE `tb_user_role`  (`role_id` int NOT NULL COMMENT '角色id',`user_id` int NOT NULL COMMENT '用户id'
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of tb_user_role
-- ----------------------------
INSERT INTO `tb_user_role` VALUES (1, 1);
INSERT INTO `tb_user_role` VALUES (2, 2);-- ----------------------------
-- Table structure for tb_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `tb_role_permission`;
CREATE TABLE `tb_role_permission`  (`role_id` int NOT NULL COMMENT '角色id',`permission_id` int NOT NULL COMMENT '权限id'
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_bin ROW_FORMAT = COMPACT;-- ----------------------------
-- Records of tb_role_permission
-- ----------------------------
INSERT INTO `tb_role_permission` VALUES (1, 1);
INSERT INTO `tb_role_permission` VALUES (1, 2);
INSERT INTO `tb_role_permission` VALUES (1, 3);
INSERT INTO `tb_role_permission` VALUES (2, 1);
INSERT INTO `tb_role_permission` VALUES (3, 1);

4. 使用mybatisplus逆向工程生成代码

具体操作我这边就不具体说了

5. 添加 自定义Realm 类

自定义Realm实现类
要求:
1.自定义Realm时,通常会继承AuthorizingRealm类,因为它同时提供了认证和授权的功能
2.重写 认证方法 doGetAuthenticationInfo() 和 授权方法doGetAuthorizationInfo() ,分别用于实现自定义的认证逻辑和授权逻辑。

细节:查询资料,得知shiro在subject.login(token)方法时不会执行doGetAuthorizationInfo方法,只有在访问到有权限验证的接口时会调用查看权限

package com.example.springbootshiro.realm;import com.example.springbootshiro.entity.Permission;
import com.example.springbootshiro.entity.Role;
import com.example.springbootshiro.entity.User;
import com.example.springbootshiro.mapper.RolePermissionMapper;
import com.example.springbootshiro.mapper.UserMapper;
import com.example.springbootshiro.mapper.UserRoleMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** @ClassName MyShiroRealm* */
@Service
public class MyShiroRealm  extends AuthorizingRealm {@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;@Autowiredprivate RolePermissionMapper rolePermissionMapper;/*** 认证方法:登录认证* @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户输入的用户名密码String username= (String) token.getPrincipal();String password=new String((char[])token.getCredentials());System.out.println("用户输入--->username:"+username+"-->password:"+password);//在数据库中查询User userInfo=userMapper.selectByName(username);if (userInfo == null) {throw new UnknownAccountException("用户名或密码错误!");}if (!password.equals(userInfo.getPassword())) {throw new IncorrectCredentialsException("用户名或密码错误!");}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名userInfo.getPassword(), // 密码getName() // realm name);return authenticationInfo;}/*** 授权方法:获取用户角色和权限* @param principal* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {if(principal == null){throw new AuthorizationException("principals should not be null");}User userInfo= (User) SecurityUtils.getSubject().getPrincipal();System.out.println("用户-->"+userInfo.getUsername()+"获取权限中");SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();//用户获取角色集List<Role> roleList=userRoleMapper.findByUserName(userInfo.getUsername());Set<String> roleSet=new HashSet<>();for (Role r:roleList){Integer roleId=r.getId();//获取角色idsimpleAuthorizationInfo.addRole(r.getName());//添加角色名字List<Permission> permissionList=rolePermissionMapper.findByRoleId(roleId);for (Permission p:permissionList){//添加权限simpleAuthorizationInfo.addStringPermission(p.getName());}}System.out.println("角色为-> " + simpleAuthorizationInfo.getRoles());System.out.println("权限为-> " + simpleAuthorizationInfo.getStringPermissions());return simpleAuthorizationInfo;}}

扩展:授权类方法 对应的 注解

授权类(SimpleAuthorizationInfo) 方法对应的 注解

角色相关的方法及注解

  1. addRole(String roleName): 添加一个角色到授权信息中。
  2. addRoles(Collection<String> roleNames): 批量添加多个角色到授权信息中。

相匹配注解:

@RequiresRoles(value = {"admin", "user"}, logical = Logical.AND): 表示方法调用者必须同时拥有"admin"和"user"两个角色。logical属性决定了多个角色或权限之间的逻辑关系(如AND、OR)。

权限相关的方法及注解

  1. addStringPermission(String permissionString): 添加一个权限字符串到授权信息中。
  2. addStringPermissions(Collection<String> permissions): 批量添加多个权限字符串到授权信息中。

相匹配注解:

@RequiresPermissions(value = {"user:create", "user:update"}, logical = Logical.OR): 表示方法调用者至少需要拥有"user:create"或"user:update"中的一个权限。

其他类别的注解
@RequiresAuthentication: 表示方法调用者必须已经通过身份验证。
@RequiresUser: 表示方法调用者必须是一个已识别的用户,但不一定是特定的角色或拥有特定的权限。

6. 添加 ShiroConfig 配置类

package com.example.springbootshiro.config;import com.example.springbootshiro.realm.MyShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;/*** @ClassName ShiroConfig**/
@Configuration
public class ShiroConfig {/*** ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理* 是shiro的大管家,相当于mybatis里的SqlSessionFactoryBean* @param securityManager* @return*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();//配置 登录页面URLshiroFilterFactoryBean.setLoginUrl("/login");//配置 登录成功后的页面URLshiroFilterFactoryBean.setSuccessUrl("/index");//配置 访问没有权限的时 触发的页面URLshiroFilterFactoryBean.setUnauthorizedUrl("/403");//页面权限控制(配置 Shiro 的过滤器链)shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap());//设置 Shiro 的安全管理器shiroFilterFactoryBean.setSecurityManager(securityManager);return shiroFilterFactoryBean;}/*** web应用管理配置* @param shiroRealm* @param cacheManager* @param manager* @return*/@Beanpublic DefaultWebSecurityManager securityManager(Realm shiroRealm, CacheManager cacheManager, RememberMeManager manager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//设置缓存管理器securityManager.setCacheManager(cacheManager);//设置记住我管理器,用于处理用户的“记住我”功能。也就是记住CookiesecurityManager.setRememberMeManager(manager);//设置单个 Realm,用于用户认证和授权。securityManager.setRealm(shiroRealm);//设置会话管理器,用于管理用户的会话。securityManager.setSessionManager(sessionManager());return securityManager;}/*** 处理 Web 会话* Session Manager:会话管理* 即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;* 会话可以是普通JavaSE环境的,也可以是如Web环境的;* @return*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();// 设置session过期时间3600sLong timeout=60L*1000*60;//毫秒级别//设置全局会话超时时间defaultWebSessionManager.setGlobalSessionTimeout(timeout);// 启用会话验证调度器,定期检查会话的有效性defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);// 禁用URL会话ID重写 ,去掉shiro登录时url里的JSESSIONIDdefaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);return defaultWebSessionManager;}/*** 加密算法* @return*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("MD5");//采用MD5 进行加密hashedCredentialsMatcher.setHashIterations(1);//加密次数return hashedCredentialsMatcher;}/*** 记住我的配置* @return*/@Beanpublic RememberMeManager rememberMeManager() {Cookie cookie = new SimpleCookie("rememberMe");cookie.setHttpOnly(true);//通过js脚本将无法读取到cookie信息cookie.setMaxAge(60 * 60 * 24);//cookie保存一天CookieRememberMeManager manager=new CookieRememberMeManager();manager.setCookie(cookie);return manager;}/*** 缓存配置* @return*/@Beanpublic CacheManager cacheManager() {MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存return cacheManager;}/*** 配置realm,用于认证和授权* @param hashedCredentialsMatcher* @return*/@Beanpublic AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {MyShiroRealm shiroRealm = new MyShiroRealm();//校验密码用到的算法
//        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);return shiroRealm;}/*** 启用shiro方言,这样能在页面上使用shiro标签* @return*/
//    @Bean
//    public ShiroDialect shiroDialect() {
//        return new ShiroDialect();
//    }/*** 开启shiro注解支持(例如:@RequiresRoles、@RequiresPermissions 等)* AuthorizationAttributeSourceAdvisor 和 DefaultAdvisorAutoProxyCreator 的共同作用下注解才能起效*/@Beanpublic AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}/***  AOP 相关的自动代理创建器* 这个才能解决权限注解不生效问题*/@Beanpublic DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();advisorAutoProxyCreator.setProxyTargetClass(true);return advisorAutoProxyCreator;}}

ShiroFilterMapFactory

抽出来的工具类 用于路径映射和拦截
可参考:Shiro拦截机制表, 里面有配置说明

package com.example.springbootshiro.config;import java.util.LinkedHashMap;/*** @ClassName ShiroFilterMapFactory*/
public class ShiroFilterMapFactory {public static LinkedHashMap<String, String> shiroFilterMap() {//设置路径映射,注意这里要用LinkedHashMap 保证有序LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();//对所有用户认证filterChainDefinitionMap.put("/static/**", "anon");filterChainDefinitionMap.put("/login", "anon");//退出拦截器,退出成功后重定向的到URL为“/”filterChainDefinitionMap.put("/logout", "logout");//对所有页面进行认证filterChainDefinitionMap.put("/**", "authc");return filterChainDefinitionMap;}
}

扩展:Shiro过滤机制表

过滤名称描述
anonorg.apache.shiro.web.filter.authc.AnonymousFilter匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例/static/**=anon
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter基于表单的拦截器;如/**=authc,如果没有登录会跳到相应的登录页面登录
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilterBasic HTTP身份验证拦截器
logoutorg.apache.shiro.web.filter.authc.LogoutFilter退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/),示例/logout=logout
noSessionCreationorg.apache.shiro.web.filter.session.NoSessionCreationFilter不创建会话拦截器,调用subject.getSession(false)不会有什么问题,但是如果subject.getSession(true)将抛出DisabledSessionException异常
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例/user/**=perms[“user:create”]
portorg.apache.shiro.web.filter.authz.PortFilter端口拦截器,主要属性port(80):可以通过的端口;示例/test= port[80],如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilterrest风格拦截器,自动根据请求方法构建权限字符串;示例/users=rest[user],会自动拼出user:read,user:create,user:update,user:delete权限字符串进行权限匹配(所有都得匹配,isPermittedAll)
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter角色授权拦截器,验证用户是否拥有所有角色;示例/admin/**=roles[admin]
sslorg.apache.shiro.web.filter.authz.SslFilterSSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口443;其他和port拦截器一样;
userorg.apache.shiro.web.filter.authc.UserFilter用户拦截器,用户已经身份验证/记住我登录的都可;示例/**=user

7. 开始测试—后端

1. 创建一个LoginController 类

用来处理登录访问请求

package com.example.springbootshiro.controller;import com.example.springbootshiro.entity.User;
import com.example.springbootshiro.result.ResponseResult;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;/*** @ClassName LoginController*/
@Controller
public class LoginController {@GetMapping("/login")public  String login(){return "login";}@GetMapping("/")public String home(){return "redirect:/index";}@GetMapping("/index")public String index(Model model){User user= (User) SecurityUtils.getSubject().getPrincipal();model.addAttribute("user",user);return "index";}@PostMapping("login")@ResponseBodypublic ResponseResult login(User user, Boolean rememberMe){System.out.println("user = " + user);UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());//获取Subject 对象Subject subject= SecurityUtils.getSubject();try {if (rememberMe){//是否记住我token.setRememberMe(true);}subject.login(token);return ResponseResult.success("/index");} catch (UnknownAccountException e) {return ResponseResult.fail(e.getMessage());} catch (IncorrectCredentialsException e) {return ResponseResult.fail(e.getMessage());}}@GetMapping("/403")public String forbid(){return "403";}
}

2. 创建一个UserController 类

用于处理User类的访问请求,并使用Shiro权限注解控制权限:

package com.example.springbootshiro.controller;import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @ClassName UserController*/
@RestController
@RequestMapping("/user")
public class UserController {@RequiresPermissions("user:queryAll")@GetMapping("/queryAll")public String queryAll(){//只演示框架...功能不实现return "查询列表";}@RequiresPermissions("user:add")@GetMapping("/add")public String userAdd(){return "添加用户";}@RequiresPermissions("user:delete")@GetMapping("/delete")public String userDelete(){return "删除用户";}
}

8. 开始测试—前端

在这里插入图片描述

1. 编写login.html页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Login Form</title><!-- 确保jQuery库被加载 --><script src="https://code.jquery.com/jquery-3.3.1.min.js"></script></head>
<body>
<form id="loginForm"><input type="text" id="username" name="username" class="text" /><input type="password" id="password" name="password" class="password" /><!-- 添加一个复选框来表示布尔值选项 --><input type="checkbox" id="rememberMe" name="rememberMe" value="true" /><label for="rememberMe">记住我</label>
</form>
<div class="signin"><input id="loginBut" type="button" value="Login" />
</div><script type="text/javascript">// jQuery插件定义$.fn.serializeObject = function () {var o = {};var a = this.serializeArray();$.each(a, function () {if (o[this.name]) {if (!o[this.name].push) {o[this.name] = [o[this.name]];}o[this.name].push(this.value);} else {o[this.name] = this.value || '';}});return o;};$(function() {$("#loginBut").click(function() {var arr = $('#loginForm').serializeObject();// 检查rememberMe复选框是否被选中if (!$('#rememberMe').is(':checked')) {// 如果未选中,则显式添加rememberMe: false到arr中arr.rememberMe = false;}$.ajax({url: '/login',type: 'post',data: arr,dataType: "json",success: function(data) {console.log("data",data,location)if (data.code == 200) {//debugger;console.log("data",data)location.href = data.message;} else {alert(data.message);}},error: function(jqXHR, textStatus, errorThrown) {// 使用jqXHR.responseText或jqXHR.responseJSON来获取错误信息if (jqXHR.responseJSON) {alert(jqXHR.responseJSON.msg || 'Unknown error');} else {alert('Error: ' + textStatus);}}});});});
</script>
</body>
</html>

2. 编写index.html页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><title>首页</title>
</head>
<body>
<h1>我是templates 下面的 index</h1>
<h1>番茄欢迎您!</h1>
<!-- 使用 th:text 和 th:if 一起检查 user 是否为 null -->
<span th:text="${user != null ? user.username : '未登录'}">登录用户:未登录</span>
<a th:href="@{/logout}">注销</a><h2>权限测试</h2>
<a th:href="@{/user/queryAll}">获取用户全部信息</a>
<a th:href="@{/user/add}">添加用户</a>
<a th:href="@{/user/delete}">删除用户</a>
</body>
</html>

3. 编写403页面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>403</title>
</head>
<body>
<h1>403权限不够</h1>
<a href="/index">首页</a>
</body>
</html>

10、测试遇到的问题

启动项目:访问http://localhost:8080/,它会自动拦截,页面重定向到 http://localhost:8080/login ,登录成功跳转到http://localhost:8080/index

问题:
登录测试用户的时候,访问没有权限的链接请求时,后台抛出Caused by:org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method 异常后,
以为会因为我们在ShiroConfig配置类中配置了shiroFilterFactoryBean.setUnauthorizedUrl(“/403”); 从而跳到我们写的403页面,
结果却并没有触发。

原因如下:
只有perms,roles,ssl,rest,port才是属于AuthorizationFilter,
而anon,authcBasic,auchc,user是AuthenticationFilter,
所以unauthorizedUrl设置后页面不跳转。

解决方案:
方案1:在ShiroFilterMapFactory.shiroFilterMap() 我们配置的 页面权限控制中 换掉 anon
方案2:要么配置全局拦截器 (推荐这种比较好)

@RestControllerAdvice
public class GlobalException {@ExceptionHandler(value = AuthorizationException.class)public String handleAuthorizationException() {return "全局处理器的 403";}

五、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)

1. 添加 maven 依赖

    <!--增加Shiro的配置--><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.4.0</version></dependency><!--Thymeleaf模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--在Thymeleaf中使用shiro标签--><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency>

2. 添加Shiro的配置

和 “SpringBoot集成Shiro权限框架(shiro-spring 方式)”相比。
去掉ShiroConfig配置类中关于ShiroFilterFactoryBean的初始化配置,移到了application.properties文件中。

properties 格式:

#开启Shiro配置,默认为true
shiro.enabled=true
#开启ShiroWeb配置,默认为true
shiro.web.enabled=true
#登录地址
shiro.loginUrl=/login
#登录成功地址
shiro.successUrl=/index
#未获授权默认跳转地址
shiro.unauthorizedUrl=/unauthorized
#是否允许将sessionId放到Url地址拦中。默认为true。默认为true
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
#是否允许将sessionId放到cookie中,默认为true
shiro.sessionManager.sessionIdCookieEnabled=true

3. 添加 ShiroConfig 配置类

和 “SpringBoot集成Shiro权限框架(shiro-spring 方式)”相比。
去掉ShiroConfig配置类中关于ShiroFilterFactoryBean的实例化方法,改为ShiroFilterChainDefinition。

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();//页面权限控制definition.addPathDefinitions(ShiroFilterMapFactory.shiroFilterMap());return definition;
}

其他的配置和操作与 “SpringBoot集成Shiro权限框架(shiro-spring 方式)” 一样,不再赘述。

参考文章
【1】SpringBoot&Shiro实现权限管理
【2】Spring Boot整合Shiro,两种方式实战总结(含源码)
【3】Springboot集成Shiro(前后端分离)
【4】SpringBoot2.0 整合 Shiro 框架,实现用户权限管理
【5】springboot整合shiro入门,实现认证和授权功能(非常详细)
【6】SpringBoot整合Shiro+Jwt实现登录认证和授权
【7】(十二)整合 Shiro 框架,实现用户权限管理-爱代码爱编程
【8】超详细 Spring Boot 整合 Shiro 教程!

相关文章:

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架

SpringBoot教程&#xff08;三十&#xff09; | SpringBoot集成Shiro权限框架 一、 什么是Shiro二、Shiro 组件核心组件其他组件 三、流程说明shiro的运行流程 四、SpringBoot 集成 Shiro &#xff08;shiro-spring-boot-web-starter方式&#xff09;1. 添加 Shiro 相关 maven2…...

[ffmpeg] 视频格式转换

本文主要梳理 ffmpeg 中的视频格式转换。由于上屏的数据是 rgba&#xff0c;编码使用的是 yuv数据&#xff0c;所以经常会使用到视频格式的转换。 除了使用 ffmpeg进行转换&#xff0c;还可以通过 libyuv 和 directX 写 shader 进行转换。 之前看到文章说 libyuv 之前是 ffmpeg…...

git-repo系列教程(3) git-repo https证书认证问题

文章目录 问题描述解决步骤1.下载证书2.测试证书是否正常3.设置环境变量 总结 问题描述 在使用git repo 同步仓库时,发现不能同步,出现如下提示错误: % Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left …...

中序遍历二叉树全过程图解

文章目录 中序遍历图解总结拓展&#xff1a;回归与回溯 中序遍历图解 首先看下中序遍历的代码&#xff0c;其接受一个根结点root作为参数&#xff0c;判断根节点是否为nil&#xff0c;不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …...

设计模式 组合模式(Composite Pattern)

组合模式简绍 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以用一致的方式处理单个对象和组合对象。这样&#xff0c;可以在不知道对象具体类型的条…...

在vue中嵌入vitepress,基于markdown文件生成静态网页从而嵌入社团周报系统的一些想法和思路

什么是vitepress vitepress是一种将markdown文件渲染成静态网页的技术 其使用仅需几行命令即可 //在根目录安装vitepress npm add -D vitepress //初始化vitepress&#xff0c;添加相关配置文件&#xff0c;选择主题&#xff0c;描述&#xff0c;框架等 npx vitepress init //…...

神经网络面试题目

1. 批规范化(Batch Normalization)的好处都有啥&#xff1f;、 A. 让每一层的输入的范围都大致固定 B. 它将权重的归一化平均值和标准差 C. 它是一种非常有效的反向传播(BP)方法 D. 这些均不是 正确答案是&#xff1a;A 解析&#xff1a; ‌‌‌‌  batch normalization 就…...

C语言题目之单身狗2

文章目录 一、题目二、思路三、代码实现 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、题目 二、思路 第一步 在c语言题目之打印单身狗我们已经讲解了在一组数据中出现一个单身狗的情况&#xff0c;而本道题是出现两个单身狗的情况。根据一个数…...

Vue2学习笔记(03关于VueComponent)

1.school组件本质是一个名为Vuecomponent的构造函数&#xff0c;且不是程序员定义的&#xff0c;是Vue.extend生成的。 2.我们只需要写<school/>或<school></school>&#xff0c;Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new Vuecompo…...

微服务架构中常用技术框架

认证授权 Spring Security OAuth 2.0 JWT Keycloak Istio Apache Shiro 日志监控 ELK Prometheus Grafana Fluentd CI/CD Jenkins GitLab CI CircleCI ArgoCD 服务通信 gRPC REST API Apache Thrift Apache Avro Apache Dubbo OpenFegin 断路器 Hystr…...

[深度学习]Pytorch框架

1 深度学习简介 应用领域:语音交互、文本处理、计算机视觉、深度学习、人机交互、知识图谱、分析处理、问题求解2 发展历史 1956年人工智能元年2016年国内开始关注深度学习2017年出现Transformer框架2018年Bert和GPT出现2022年,chatGPT出现,进入AIGC发展阶段3 PyTorch框架简…...

华为HarmonyOS灵活高效的消息推送服务(Push Kit) - 5 发送通知消息

场景介绍 通知消息通过Push Kit通道直接下发&#xff0c;可在终端设备的通知中心、锁屏、横幅等展示&#xff0c;用户点击后拉起应用。您可以通过设置通知消息样式来吸引用户。 开通权益 Push Kit根据消息内容&#xff0c;将通知消息分类为服务与通讯、资讯营销两大类别&…...

[Meachines] [Medium] Querier XLSM宏+MSSQL NTLM哈希窃取(xp_dirtree)+GPP凭据泄露

信息收集 IP AddressOpening Ports10.10.10.125TCP:135, 139, 445, 1433, 5985, 47001, 49664, 49665, 49666, 49667, 49668, 49669, 49670, 49671 $ nmap -p- 10.10.10.125 --min-rate 1000 -sC -sV -Pn PORT STATE SERVICE VERSION 135/tcp open msrp…...

新版ssh客户端无法连接旧版服务器sshd的方法

新安装完的windows 版本&#xff0c;连Linux服务器直接报错 C:\Users\wang>ssh root192.168.110.50 Unable to negotiate with 192.168.110.50 port 22: no matching key exchange method found. Their offer: diffie-hellman-group14-sha1,diffie-hellman-group1-sha1,kex…...

MyBatis操作数据库-XML实现

目录 1.MyBatis的简单介绍 2.MyBatis操作数据库的步骤 2.1 添加依赖 2.2 配置文件 2.3 写持久层代码 2.4 方法测试 3.MyBatis操作数据库(增删查改) 3.1 CRUD标签 3.2 参数传递 3.3 Insert-新增 3.4 Delete-删除 3.5 Update-修改 3.6 Select-查询(映射问题) 1.MyB…...

华为HarmonyOS地图服务 5 - 利用UI控件和手势进行地图交互

场景介绍 本章节将向您介绍如何使用地图的手势。 Map Kit提供了多种手势供用户与地图之间进行交互&#xff0c;如缩放、滚动、旋转和倾斜。这些手势默认开启&#xff0c;如果想要关闭某些手势&#xff0c;可以通过MapComponentController类提供的接口来控制手势的开关。 接口…...

解决DockerDesktop启动redis后采用PowerShell终端操作

如图&#xff1a; 在启动redis容器后&#xff0c;会计入以下界面 &#xff1a; 在进入执行界面后如图&#xff1a; 是否会觉得界面过于单调&#xff0c;于是想到使用PowerShell来操作。 步骤如下&#xff1a; 这样就能使用PowerShell愉快地敲命令了&#xff08;颜值是第一生…...

react + antDesign封装图片预览组件(支持多张图片)

需求场景&#xff1a;最近在开发后台系统时经常遇到图片预览问题&#xff0c;如果一个一个的引用antDesign的图片预览组件就有点繁琐了&#xff0c;于是在antDesign图片预览组件的基础上二次封装了一下&#xff0c;避免重复无用代码的出现 效果 公共预览组件代码 import React…...

逻辑回归 和 支持向量机(SVM)比较

为了更好地理解为什么在二分类问题中使用 SVM&#xff0c;逻辑回归的区别&#xff0c;我们需要深入了解这两种算法的区别、优势、劣势&#xff0c;以及它们适用于不同场景的原因。 逻辑回归和 SVM 的比较 1. 模型的核心思想 • 逻辑回归&#xff1a; • 基于概率的模型&…...

GS-SLAM论文阅读笔记--TAMBRIDGE

前言 本文提出了一个自己的分类方法&#xff0c;传统的视觉SLAM通常使用以帧为中心的跟踪方法&#xff0c;但是3DGS作为一种高效的地图表达方法好像更侧重于地图的创建。这两种方法都有各自的优缺点&#xff0c;但是如果能取长补短&#xff0c;互相结合&#xff0c;那么就会是…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

五年级数学知识边界总结思考-下册

目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解&#xff1a;由来、作用与意义**一、知识点核心内容****二、知识点的由来&#xff1a;从生活实践到数学抽象****三、知识的作用&#xff1a;解决实际问题的工具****四、学习的意义&#xff1a;培养核心素养…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说&#xff0c;直接开始吧&#xff01; 一、基础概念 1. 哈希核心思想&#xff1a; 哈希函数的作用&#xff1a;通过此函数建立一个Key与存储位置之间的映射关系。理想目标&#xff1a;实现…...

门静脉高压——表现

一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构&#xff1a;由肠系膜上静脉和脾静脉汇合构成&#xff0c;是肝脏血液供应的主要来源。淤血后果&#xff1a;门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血&#xff0c;引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

CVE-2023-25194源码分析与漏洞复现(Kafka JNDI注入)

漏洞概述 漏洞名称&#xff1a;Apache Kafka Connect JNDI注入导致的远程代码执行漏洞 CVE编号&#xff1a;CVE-2023-25194 CVSS评分&#xff1a;8.8 影响版本&#xff1a;Apache Kafka 2.3.0 - 3.3.2 修复版本&#xff1a;≥ 3.4.0 漏洞类型&#xff1a;反序列化导致的远程代…...

vue3 手动封装城市三级联动

要做的功能 示意图是这样的&#xff0c;因为后端给的数据结构 不足以使用ant-design组件 的联动查询组件 所以只能自己分装 组件 当然 这个数据后端给的不一样的情况下 可能组件内对应的 逻辑方式就不一样 毕竟是 三个 数组 省份 城市 区域 我直接粘贴组件代码了 <temp…...

ubuntu系统 | docker+dify+ollama+deepseek搭建本地应用

1、docker 介绍与安装 docker安装:1、Ubuntu系统安装docker_ubuntu docker run-CSDN博客 docker介绍及镜像源配置:2、ubuntu系统docker介绍及镜像源和仓库配置-CSDN博客 docker常用命令:3、ubuntu系统docker常用命令-CSDN博客 docker compose安装:4、docker compose-CS…...

Amazon RDS on AWS Outposts:解锁本地化云数据库的混合云新体验

在混合云架构成为企业数字化转型标配的今天&#xff0c;如何在本地数据中心享受云数据库的强大能力&#xff0c;同时满足数据本地化、低延迟访问的严苛需求&#xff1f;Amazon RDS on AWS Outposts 给出了完美答案——将AWS完全托管的云数据库服务无缝延伸至您的机房&#xff0…...