SpringBoot教程(三十) | SpringBoot集成Shiro权限框架(shiro-spring 方式)

news2024/11/16 8:22:35

SpringBoot教程(三十) | SpringBoot集成Shiro权限框架(shiro-spring方式)

  • 一、 什么是Shiro
  • 二、Shiro 组件
    • 核心组件
    • 其他组件
  • 三、流程说明
    • shiro的运行流程
  • 四、SpringBoot 集成 Shiro
    • 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、测试遇到的问题

一、 什么是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

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 {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RolePermissionMapper rolePermissionMapper;


    /**
     * 认证方法:登录认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected 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
     */
    @Override
    protected 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();//获取角色id
            simpleAuthorizationInfo.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
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //配置 登录页面URL
        shiroFilterFactoryBean.setLoginUrl("/login");
        //配置 登录成功后的页面URL
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //配置 访问没有权限的时 触发的页面URL
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        //页面权限控制(配置 Shiro 的过滤器链)
        shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap());
        //设置 Shiro 的安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    /**
     * web应用管理配置
     * @param shiroRealm
     * @param cacheManager
     * @param manager
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(Realm shiroRealm, CacheManager cacheManager, RememberMeManager manager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置缓存管理器
        securityManager.setCacheManager(cacheManager);
        //设置记住我管理器,用于处理用户的“记住我”功能。也就是记住Cookie
        securityManager.setRememberMeManager(manager);
        //设置单个 Realm,用于用户认证和授权。
        securityManager.setRealm(shiroRealm);
        //设置会话管理器,用于管理用户的会话。
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    /**
     * 处理 Web 会话
     * Session Manager:会话管理
     * 即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;
     * 会话可以是普通JavaSE环境的,也可以是如Web环境的;
     * @return
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();
        // 设置session过期时间3600s
        Long timeout=60L*1000*60;//毫秒级别
        //设置全局会话超时时间
        defaultWebSessionManager.setGlobalSessionTimeout(timeout);
        // 启用会话验证调度器,定期检查会话的有效性
        defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
        // 禁用URL会话ID重写 ,去掉shiro登录时url里的JSESSIONID
        defaultWebSessionManager.setSessionIdUrlRewritingEnabled(false);
        return defaultWebSessionManager;
    }

    /**
     * 加密算法
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");//采用MD5 进行加密
        hashedCredentialsMatcher.setHashIterations(1);//加密次数
        return hashedCredentialsMatcher;
    }

    /**
     * 记住我的配置
     * @return
     */
    @Bean
    public 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
     */
    @Bean
    public CacheManager cacheManager() {
        MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存
        return cacheManager;
    }

    /**
     * 配置realm,用于认证和授权
     * @param hashedCredentialsMatcher
     * @return
     */
    @Bean
    public 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 的共同作用下注解才能起效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    /**
     *  AOP 相关的自动代理创建器
     * 这个才能解决权限注解不生效问题
     */
    @Bean
    public 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")
    @ResponseBody
    public 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";

    }

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2154406.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链表(3)链表的基本操作

单链表的基本操作主要有;①创建链表;②输出链表;③査我结点;④插入结点,⑤鹏除结点;⑥重组链表。下面分别进行介绍。 一.创建链表 创建链表是指在程序运行时,进行动态内存分配,创建若千个结点,并把这些结点连接成串,形成一个链表。在进行动态内存分配时,需要使用在&#xff08…

QT快速安装使用指南

在Ubuntu 16.04上安装Qt可以通过多种方式进行。以下是使用Qt在线安装程序和apt包管理器的两种常见方法&#xff1a; 方法一&#xff1a;使用Qt在线安装程序 下载Qt在线安装程序 访问Qt官方网站&#xff1a;Try Qt | Develop Applications and Embedded Systems | Qt找到并下载…

Swift里的数值变量的最大值和最小值

Swift里有很多种数值变量&#xff0c;如Int&#xff0c;Int8&#xff0c;Float&#xff0c;Double等。和绝大多数编程语言一样&#xff0c;由于是在计算机上运行&#xff0c;内存有限&#xff0c;所以必有最大值和最小值&#xff0c;而计算机无法处理超过该值的数。 在Swift中…

【Linux】POSIX信号量、基于环形队列实现的生产者消费者模型

目录 一、POSIX信号量概述 信号量的基本概念 信号量在临界区的作用 与互斥锁的比较 信号量的原理 信号量的优势 二、信号量的操作 1、初始化信号量&#xff1a;sem_init 2、信号量申请&#xff08;P操作&#xff09;&#xff1a;sem_wait 3、信号量的释放&#xff08…

网络安全-webshell绕过,hash碰撞,webshell绕过原理

目录 一、题目 1.1 1.2 1.3 1.4 1.5 二、绕过动态检测引擎的一次尝试 三、一个比赛中的webshell 四、webshell绕过的原理以及哈希碰撞 五、JSP解释流程导致的绕过&#xff08;QT比赛&#xff09; 5.1环境 5.2例子 一、题目 这里我们通过几道题目来给大家讲解 1.…

UI自动化测试框架搭建详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 今天给大家分享一个seleniumtestngmavenant的UI自动化&#xff0c;可以用于功能测试&#xff0c;也可按复杂的业务流程编写测试用例&#xff0c;今天此篇文章不过…

【HTML样式】加载动画专题 每周更新

加载动画专题 煎蛋加载动画方块移动加载动画电子风变脸正方体组合跳跃式加载动画 煎蛋加载动画 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width…

计算机毕业设计之:基于微信小程序的校园流浪猫收养系统(源码+文档+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

6种解决msvcp140_ATOMIC_WAIT.dll丢失的方法分享

日常生活工作中&#xff0c;电脑已经成为我们生活和工作中不可或缺的工具。然而&#xff0c;在使用过程中&#xff0c;我们也会遇到各种问题&#xff0c;其中之一就是电脑中的msvcp140_ATOMIC_WAIT.dll文件丢失。这个问题可能会导致电脑运行不稳定&#xff0c;甚至无法正常启动…

数据结构之线性表——LeetCode:328. 奇偶链表,86. 分隔链表,24. 两两交换链表中的节点

328. 奇偶链表 题目描述 328. 奇偶链表 给定单链表的头节点 head &#xff0c;将所有索引为奇数的节点和索引为偶数的节点分别组合在一起&#xff0c;然后返回重新排序的列表。 第一个节点的索引被认为是 奇数 &#xff0c; 第二个节点的索引为 偶数 &#xff0c;以此类推。…

华为全联接大会HC2024 观会感

9/19-21于上海&#xff0c;华为举办了他一年一届也是最重要的华为系展会-Huawei Connect 华为全联接大会&#xff0c;今天有幸赶在展会最后一天来参观一下 上午照常是keynote&#xff0c;由华为计算线总裁进行了今天的KN开场&#xff0c;介绍了华为在“算”方面的进展&#x…

Java | Leetcode Java题解之第420题强密码检验器

题目&#xff1a; 题解&#xff1a; class Solution {public int strongPasswordChecker(String password) {int n password.length();int hasLower 0, hasUpper 0, hasDigit 0;for (int i 0; i < n; i) {char ch password.charAt(i);if (Character.isLowerCase(ch))…

企业内训|LLM大模型实战技术深度研修-某智算厂商研发中心

课程概要 本课程深入研修LLM大模型在实际应用中的技术实现和优化策略。通过迁移与适配、训练与调优、推理优化以及综合应用与案例分析四个模块&#xff0c;系统地探讨大模型的核心理论、关键技术和实践操作。课程内容涵盖模型迁移的理论与实操、预训练与微调策略、推理性能优化…

[数据结构与算法·C++版] 笔记 1.2 什么是数据结构

1.2 什么是数据结构 结构&#xff1a;实体 关系数据结构&#xff1a; 按照逻辑关系组织起来的一批数据&#xff0c;按一定的存储方法把它存储在计算机中在这些数据上定义了一个运算的集合 数据结构的逻辑组织 线性结构 线性表&#xff08;表&#xff0c;栈&#xff0c;队列&…

新版torch_geometric不存在uniform、maybe_num_nodes函数问题(Prune4ED论文报错解决)

这是在复现论文“Towards accurate subgraph similarity computation via neural graph pruning”时遇到的报错。 ImportError: cannot import name uniform from torch_geometric.nn.pool.topk_pool 一、报错原因 论文作者使用的是2.1.0版本的torch_geometric。而我安装了2.…

Google 扩展 Chrome 安全和隐私功能

过去一周&#xff0c;谷歌一直在推出新特性和功能&#xff0c;旨在让用户在 Chrome 上的桌面体验更加安全&#xff0c;最新的举措是扩展在多个设备上保存密钥的功能。 到目前为止&#xff0c;Chrome 网络用户只能将密钥保存到 Android 上的 Google 密码管理器&#xff0c;然后…

springboot实战学习(6)(用户模块的登录认证)(初识令牌)(JWT)

接着上篇博客学习。上篇博客是在基本完成用户模块的注册接口的开发以及注册时的参数合法性校验的基础上&#xff0c;基本完成用户模块的登录接口的主逻辑。具体往回看了解的链接如下。 springboot实战学习笔记&#xff08;5&#xff09;(用户登录接口的主逻辑)-CSDN博客文章浏览…

爬虫学习 | 03 爬虫静态网页的爬取(1)

学习的资料是&#xff1a;python chatgpt 网络爬虫从入门到精通 目录 Step1&#xff1a;基本的环境 Step2&#xff1a;ai辅助解决问题实现代码功能&#xff1a; Step3&#xff1a;网页的初步分析&#xff1a; Step4&#xff1a;静态网页的爬取 爬取信息&#xff1a; 实操…

MQ入门(4)

Erlang&#xff1a;面向高并发的 单机的吞吐量就是并发性&#xff1a;Rabbitmq是10w左右&#xff08;现实项目中已经足够用了&#xff09;&#xff0c;RocketMQ是10w到20w&#xff0c;Kafka是100w左右。 公司里的并发&#xff08;QPS&#xff09; 大部分的公司每天的QPS大概…

【CSS in Depth 2 精译_036】5.6 Grid 网格布局中与对齐相关的属性 + 5.7本章小结

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一章 层叠、优先级与继承&#xff08;已完结&#xff09; 1.1 层叠1.2 继承1.3 特殊值1.4 简写属性1.5 CSS 渐进式增强技术1.6 本章小结 第二章 相对单位&#xff08;已完结&#xff09; 2.1 相对…