zoukankan      html  css  js  c++  java
  • SpringBoot+Shiro+Redis整合以及实现记住我(RememberMe)功能

    前言:

    Shiro中本身就提供了sessionManager和sessionDAO,我们可以把shiro和redis集成起来,把session持久化到Redis中,需要使用的时候从Redis中可以获取对应的session。

    本章介绍如下几个功能:

    1.当用户没有登陆时只能访问登陆界面

    2.当用户登陆成功后,只能访问该用户下仅有的权限

    3.记住登录用户(rememberMe)

    4.一个账号可以多人同时登录

    说明:本章案例做了简化,仅作为springboot+shiro+redis项目整合为参考,适合入门使用,亲测有效。

     

    一、数据库设计

    表设计思路:用户对应角色,角色包含拥有的菜单和其他权限,菜单也对应着某个权限,说明有这个菜单就有对应的权限(权限表包含菜单ID),权限表里不设置菜单ID就是其他权限。

     1.SQL

    /*
     Navicat Premium Data Transfer
    
     Source Server         : localhost
     Source Server Type    : MySQL
     Source Server Version : 50712
     Source Host           : localhost:3306
     Source Schema         : boot_shiro_redis
    
     Target Server Type    : MySQL
     Target Server Version : 50712
     File Encoding         : 65001
    
     Date: 10/03/2020 17:09:41
    */
    
    SET NAMES utf8mb4;
    SET FOREIGN_KEY_CHECKS = 0;
    
    -- ----------------------------
    -- Table structure for manage_menu
    -- ----------------------------
    DROP TABLE IF EXISTS `manage_menu`;
    CREATE TABLE `manage_menu`  (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '路径名称',
      `ICON` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '图标class(el)',
      `URL` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '路径地址',
      `PARENT_ID` int(11) NULL DEFAULT NULL COMMENT '父节点ID,父节点同样在本目录下',
      PRIMARY KEY (`ID`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜单管理表' ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of manage_menu
    -- ----------------------------
    INSERT INTO `manage_menu` VALUES (1, '首页', 'el-icon-s-home', '/index', NULL);
    INSERT INTO `manage_menu` VALUES (2, '权限管理', 'fa fa-book', '/managePermission/getPermissionAll', NULL);
    INSERT INTO `manage_menu` VALUES (3, '人员管理', 'fa fa-book', '/manageUser/getUserAll', 2);
    
    -- ----------------------------
    -- Table structure for manage_permission
    -- ----------------------------
    DROP TABLE IF EXISTS `manage_permission`;
    CREATE TABLE `manage_permission`  (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
      `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称',
      `RESOURCE` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '资源地址',
      `SN` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '描述',
      `MENU_ID` int(11) NULL DEFAULT NULL COMMENT '菜单表中的ID',
      PRIMARY KEY (`ID`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '路径权限' ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of manage_permission
    -- ----------------------------
    INSERT INTO `manage_permission` VALUES (1, '查看所有权限', '/managePermission/getPermissionAll', 'managePermission:list', 3);
    INSERT INTO `manage_permission` VALUES (2, '查看所有人员', '/manageUser/getUserAll', 'manageUser:list', NULL);
    
    -- ----------------------------
    -- Table structure for manage_roles
    -- ----------------------------
    DROP TABLE IF EXISTS `manage_roles`;
    CREATE TABLE `manage_roles`  (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
      `NAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名',
      `MENUS_ID` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜单的ID(多个菜单由逗号分隔)',
      `PERMISSIONS_ID` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '其余权限的ID(多个权限由逗号分隔)',
      PRIMARY KEY (`ID`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限' ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of manage_roles
    -- ----------------------------
    INSERT INTO `manage_roles` VALUES (1, '管理员', '1,2,3', '3');
    INSERT INTO `manage_roles` VALUES (2, '普通用户', '1', NULL);
    
    -- ----------------------------
    -- Table structure for manage_user
    -- ----------------------------
    DROP TABLE IF EXISTS `manage_user`;
    CREATE TABLE `manage_user`  (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '管理员ID',
      `USERNAME` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登陆名',
      `PASSWORD` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '密码',
      `ROLE_ID` int(11) NOT NULL DEFAULT 0 COMMENT '对应的角色Id',
      PRIMARY KEY (`ID`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '登录用户' ROW_FORMAT = Dynamic;
    
    -- ----------------------------
    -- Records of manage_user
    -- ----------------------------
    INSERT INTO `manage_user` VALUES (1, 'admin', '4ec847db9bc2bad60e4279cce1fad5db', 1);
    INSERT INTO `manage_user` VALUES (4, 'user', '4e0374eaa5fd58d90a549cac95a657ab', 2);
    
    SET FOREIGN_KEY_CHECKS = 1;
    View Code

    二、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.1.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>demo</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <packaging>war</packaging>
        <name>demo</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-web</artifactId>
            </dependency>
    
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!--mysql数据库连接驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <!-- mybatis-plus-boot-starter -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>2.2.0</version>
            </dependency>
            <!-- 模板引擎 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>2.0</version>
            </dependency>
    
            <!--lombok代码简化工具-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.16.6</version>
            </dependency>
    
            <!-- shiro spring. -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-ehcache</artifactId>
                <version>1.4.0</version>
            </dependency>
            <!--使用的是shiro-redis开源插件-->
            <dependency>
                <groupId>org.crazycake</groupId>
                <artifactId>shiro-redis</artifactId>
                <version>2.4.2.1-RELEASE</version>
                <exclusions>
                    <exclusion>
                        <artifactId>shiro-core</artifactId>
                        <groupId>org.apache.shiro</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>shiro-core</artifactId>
                        <groupId>org.apache.shiro</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>jedis</artifactId>
                        <groupId>redis.clients</groupId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
                <version>2.9.0</version>
            </dependency>
    
            <!--redis-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
    
            <!--thymeleaf 页面模板依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
                <scope>provided</scope>
            </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>
    View Code

    三、application.yml配置redis

    spring
        redis:
          database: 1
          host: 127.0.0.1
          port: 6379
          password:       # 密码(默认为空)
          timeout: 6000  # 连接超时时长(毫秒)
          jedis:
            pool:
              max-active: 1000  # 连接池最大连接数(使用负值表示没有限制)
              max-wait: -1     # 连接池最大阻塞等待时间(使用负值表示没有限制)
              max-idle: 10      # 连接池中的最大空闲连接
              min-idle: 5       # 连接池中的最小空闲连接
    View Code

    四、添加配置类

    1.shiro配置类

    package com.example.demo.config;
    
    import com.example.demo.shiro.MySessionManager;
    import com.example.demo.shiro.realms.UserRealm;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.session.mgt.SessionManager;
    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.SimpleCookie;
    import org.crazycake.shiro.RedisCacheManager;
    import org.crazycake.shiro.RedisManager;
    import org.crazycake.shiro.RedisSessionDAO;
    import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * @author:
     * @since:
     * @description:
     */
    @Configuration
    public class ShiroConfig {
    
        @Value("${spring.redis.host}")
        private String host;
        @Value("${spring.redis.port}")
        private Integer port;
        @Value("${spring.redis.timeout}")
        private Integer timeout;
        @Value("${spring.redis.password}")
        private String password;
    
        @Bean
        ShiroFilterFactoryBean shiroFilterFactoryBean() {
            
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            bean.setSecurityManager(securityManager());// 必须设置 SecurityManager安全管理器
    
            bean.setSuccessUrl("/index");
            bean.setUnauthorizedUrl("/unauthorizedurl");
    
            Map<String, String> map = new LinkedHashMap<>();
    
            //匿名使用
            map.put("/login", "anon");
            map.put("/loginPage","anon");
            map.put("/logout", "logout");// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
    
    
            map.put("/**","authc");
    
            //配置记住我或认证通过可以访问的地址
            map.put("/**", "user");
            bean.setLoginUrl("/loginPage");
            bean.setFilterChainDefinitionMap(map);
            return bean;
    
        }
        /**
         * 开启shiro aop注解支持. 使用代理方式;
         * @param securityManager
         * @return
         */
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
        /**
         * DefaultAdvisorAutoProxyCreator实现了BeanProcessor接口,
         * 当ApplicationContext读如所有的Bean配置信息后,这个类将扫描上下文,
         * 找出所有的Advistor(一个切入点和一个通知的组成),将这些Advisor应用到所有符合切入点的Bean中
         * @return
         */
        @Bean
        public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
            DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
            advisorAutoProxyCreator.setProxyTargetClass(true);
            return advisorAutoProxyCreator;
        }
    
    
        @Bean
        public DefaultWebSecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myRealm());
            // 自定义session管理 使用redis
            securityManager.setSessionManager(sessionManager());
            // 自定义缓存实现 使用redis
            securityManager.setCacheManager(cacheManager());
            //自定义rememberMe //把cookie管理器交给SecurityManager
            securityManager.setRememberMeManager(rememberMeManager());
            return securityManager;
        }
        @Bean
        public UserRealm myRealm() {
            UserRealm userRealm = new UserRealm();
            // 配置 加密 (在加密后,不配置的话会导致登陆密码失败)
            userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return userRealm;
        }
        /**
         * 密码校验规则HashedCredentialsMatcher
         * 这个类是为了对密码进行编码的 ,
         * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
         * 这个类也负责对form里输入的密码进行编码
         * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            //指定加密方式为MD5
            credentialsMatcher.setHashAlgorithmName("MD5");
            //加密次数
            credentialsMatcher.setHashIterations(1024);
            //此时用的是密码加密用的是 Hex 编码; false 时用 Base64 编码
            credentialsMatcher.setStoredCredentialsHexEncoded(true);
            return credentialsMatcher;
        }
    
        //自定义sessionManager
        @Bean
        public SessionManager sessionManager() {
            MySessionManager mySessionManager = new MySessionManager();
            mySessionManager.setSessionIdUrlRewritingEnabled(false);//url 不显示sessionID
            mySessionManager.setSessionDAO(redisSessionDAO());
            return mySessionManager;
        }
        /**
         * RedisSessionDAO shiro sessionDao层的实现 通过redis
         * 使用的是shiro-redis开源插件
         */
        @Bean
        public RedisSessionDAO redisSessionDAO() {
            RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
            redisSessionDAO.setRedisManager(redisManager());
            return redisSessionDAO;
        }
    
        //@Bean
        public RedisCacheManager cacheManager() {
            RedisCacheManager redisCacheManager = new RedisCacheManager();
            redisCacheManager.setRedisManager(redisManager());
            return redisCacheManager;
        }
        public RedisManager redisManager() {
            RedisManager redisManager = new RedisManager();
            redisManager.setHost(host);
            redisManager.setExpire(1800);// 配置缓存过期时间
            redisManager.setTimeout(timeout);
            redisManager.setPort(port);
            redisManager.setPassword(password);
            return redisManager;
        }
    
        /**
         * cookie管理对象;
         * rememberMeManager()方法是生成rememberMe管理器,而且要将这个rememberMe管理器设置到securityManager中
         * @return
         */
        public CookieRememberMeManager rememberMeManager(){
            CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
            cookieRememberMeManager.setCookie(rememberMeCookie());
            return cookieRememberMeManager;
        }
        public SimpleCookie rememberMeCookie(){
            SimpleCookie simpleCookie = new SimpleCookie();
            simpleCookie.setName("boot-shiro-rememberMe");
            simpleCookie.setMaxAge(200000);//设置cookie的生效时间
            simpleCookie.setHttpOnly(true);
            return simpleCookie;
        }
    }
    View Code

     1.1自定义MySessionManager 获取sessionId

    public class MySessionManager extends DefaultWebSessionManager {
    
        private static final String AUTHORIZATION = "Authorization";
    
        private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    
        public MySessionManager() {
            super();
        }
    
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
            //如果请求头中有 Authorization 则其值为sessionId
            if (!StringUtils.isEmpty(id)) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return id;
            } else {
                //否则按默认规则从cookie取sessionId
                return super.getSessionId(request, response);
            }
        }
    }
    View Code

    2.redis配置类

    package com.example.demo.config;
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.cache.annotation.CachingConfigurerSupport;
    import org.springframework.cache.annotation.EnableCaching;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.*;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * redis配置类
     * @program: springbootdemo
     * @Description:
     */
    @Configuration
    @EnableCaching //开启注解
    public class RedisConfig extends CachingConfigurerSupport {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Bean
        public RedisTemplate<String, Object> stringSerializerRedisTemplate(){
            RedisSerializer<String> stringSerializer = new StringRedisSerializer();
            redisTemplate.setKeySerializer(stringSerializer);
            redisTemplate.setValueSerializer(stringSerializer);
            redisTemplate.setHashKeySerializer(stringSerializer);
            redisTemplate.setHashValueSerializer(stringSerializer);
            return redisTemplate;
        }
    
        /**
         * retemplate相关配置
         * @param factory
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    
            RedisTemplate<String, Object> template = new RedisTemplate<>();
            // 配置连接工厂
            template.setConnectionFactory(factory);
    
            //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
            Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
    
            ObjectMapper om = new ObjectMapper();
            // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            jacksonSeial.setObjectMapper(om);
    
            // 值采用json序列化
            template.setValueSerializer(jacksonSeial);
            //使用StringRedisSerializer来序列化和反序列化redis的key值
            template.setKeySerializer(new StringRedisSerializer());
    
            // 设置hash key 和value序列化模式
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(jacksonSeial);
            template.afterPropertiesSet();
    
            return template;
        }
    
        /**
         * 对hash类型的数据操作
         *
         * @param redisTemplate
         * @return
         */
        @Bean
        public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForHash();
        }
    
        /**
         * 对redis字符串类型数据操作
         *
         * @param redisTemplate
         * @return
         */
        @Bean
        public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForValue();
        }
    
        /**
         * 对链表类型的数据操作
         *
         * @param redisTemplate
         * @return
         */
        @Bean
        public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForList();
        }
    
        /**
         * 对无序集合类型的数据操作
         *
         * @param redisTemplate
         * @return
         */
        @Bean
        public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForSet();
        }
        /**
         * 对有序集合类型的数据操作
         *
         * @param redisTemplate
         * @return
         */
        @Bean
        public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
            return redisTemplate.opsForZSet();
        }
    
    }
    View Code

     五、自定义Realm

    package com.example.demo.shiro.realms;
    
    import com.baomidou.mybatisplus.mapper.EntityWrapper;
    import com.example.demo.entity.ManagePermission;
    import com.example.demo.entity.ManageRoles;
    import com.example.demo.entity.ManageUser;
    import com.example.demo.service.ManagePermissionService;
    import com.example.demo.service.ManageRolesService;
    import com.example.demo.service.ManageUserService;
    import org.apache.commons.lang3.StringUtils;
    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.crypto.hash.SimpleHash;
    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.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * @author:
     * @since:
     * @description:
     */
    public class UserRealm extends AuthorizingRealm {
        @Autowired
        private ManageUserService userService;
    
        @Autowired
        private ManageRolesService manageRolesService;
    
        @Autowired
        private ManagePermissionService managePermissionService;
        //执行授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
            // TODO Auto-generated method stub
            System.out.println("授权");
            //获取当前登录用户
            Subject subject = SecurityUtils.getSubject();
            ManageUser user = (ManageUser) subject.getPrincipal();
    
            //查询用户对应的角色
            Integer roleId = user.getRoleId();
            ManageRoles manageRoles = manageRolesService.selectById(roleId);
    
            if(manageRoles!=null){//角色不为空
                //获取角色对应的菜单
                String menus = manageRoles.getMenusId();
                String[] menuId = menus.split(",");
    
                //菜单对应的权限
                List<ManagePermission> menuPermission = managePermissionService.selectList(new EntityWrapper<ManagePermission>().in("MENU_ID", Arrays.asList(menuId)));
    
                //其他相关权限
                List<ManagePermission> managePermissions = new ArrayList<>();
                String permissions = manageRoles.getPermissionsId();
                if(StringUtils.isNotBlank(permissions)){
                    String[] permissionsId = permissions.split(",");
                     managePermissions = managePermissionService.selectBatchIds(Arrays.asList(permissionsId));
                }
    
                //当前登录用户的所有权限
                managePermissions.addAll(menuPermission);
                List<String> sn = managePermissions.stream().map(ManagePermission::getSn).collect(Collectors.toList());
    
                System.out.println(String.valueOf(sn));
                //给资源授权
                SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
                simpleAuthorizationInfo.addStringPermissions(sn);
                simpleAuthorizationInfo.addRole(manageRoles.getName());
                return simpleAuthorizationInfo;
            }
            return null;
        }
        //执行认证逻辑
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            // TODO Auto-generated method stub
            System.out.println("认证");
    
            //shiro判断逻辑
            UsernamePasswordToken user = (UsernamePasswordToken) token;
            ManageUser realUser = new ManageUser();
            realUser.setUsername(user.getUsername());
            realUser.setPassword(String.copyValueOf(user.getPassword()));
    
            ManageUser newUser = userService.selectOne(new EntityWrapper<ManageUser>().eq("USERNAME",realUser.getUsername()));
            if(newUser == null){
                //用户名错误
                //shiro会抛出UnknownAccountException异常
                return null;
            }
            System.out.println("认证用户:"+realUser);
            ByteSource credentialsSalt = ByteSource.Util.bytes(newUser.getUsername());
            return new SimpleAuthenticationInfo(newUser,newUser.getPassword(),credentialsSalt,getName());
        }
        /**
         * 在使用登出功能的时候,会触发的回调
         * @param principals
         */
        @Override
        public void onLogout(PrincipalCollection principals) {
            super.onLogout(principals);
        }
    
        public static void main(String[] args) {
            String hashAlgorithName = "MD5";
            String password = "root";
            int hashIterations = 1024;//加密次数
            ByteSource credentialsSalt = ByteSource.Util.bytes("admin");//盐值
            Object obj = new SimpleHash(hashAlgorithName, password, credentialsSalt, hashIterations);
            System.out.println(obj);
        }
    }
    View Code

    六、VO层

    1.菜单树

    @Data
    @Builder
    @NoArgsConstructor
    @EqualsAndHashCode
    @AllArgsConstructor
    @Accessors(chain = true)
    public class ManageMenuVO implements Serializable {
    
        private Integer id;
        /**
         * 路径名称
         */
        private String name;
        /**
         * 图标class(elementui)
         */
        private String icon;
        /**
         * 路径地址
         */
        private String url;
        /**
         * 父节点ID,父节点同样在本目录下
         */
        private Integer parentId;
    
    
        private List<ManageMenuVO> childrens;
    
    }
    View Code

    七、Controller层

    1.登录

    package com.example.demo.controller;
    
    import lombok.extern.slf4j.Slf4j;
    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.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author:
     * @since:
     * @description:
     */
    @Slf4j
    @Controller
    public class LoginController {
    
        @PostMapping(value = "/login")
        public String createUser(String username, String password, String rememberMe, Model model) {
            Map restMap = new HashMap<String,Object>();
            Integer code = 200;
            String msg = "登录成功";
            Object data = null;
    
            Session session = SecurityUtils.getSubject().getSession();
            log.info("登录的sessionId  "+session.getId());
    
            //获取Subject
            Subject subject = SecurityUtils.getSubject();
            //封装用户数据
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
            //执行登录方法
            try {
                if(rememberMe!=null){token.setRememberMe(Boolean.parseBoolean(rememberMe));}
                subject.login(token);
                //登录成功
                return "redirect:index";
            } catch (UnknownAccountException e) {
                code = 500;
                msg = "用户名错误";
                data = e;
            } catch (IncorrectCredentialsException e) {
                code = 500;
                msg = "密码错误";
                data = e;
            }
            restMap.put("code",code);
            restMap.put("msg",msg);
            restMap.put("data",data);
            model.addAllAttributes(restMap);
            return "err";
        }
    
         @RequestMapping(value = "/logout")
         public void logout() {
             System.err.println("退出");
             Subject subject = SecurityUtils.getSubject();
             if (subject.isAuthenticated()) {
                 subject.logout(); // session 会销毁,在sessionlistener监听session销毁,清理权限缓存
             }
         }
    }
    View Code

    2.页面跳转

    @Controller
    public class RouterController {
    
        @Autowired
        private ManageMenuService manageMenuService;
    
        @GetMapping("/loginPage")
        public String loginPage(){
            return "/loginPage";
        }
    
        @GetMapping("/index")
        public String indexPage(Model model){
            //获取Subject
            Subject subject = SecurityUtils.getSubject();
            Map restMap = new HashMap<String,Object>();
            restMap.put("menuList",manageMenuService.selectMenuTree());
            restMap.put("data",String.valueOf(subject.getSession().getId()));
            System.out.println(restMap);
            model.addAllAttributes(restMap);
            return "/index";
        }
    }
    View Code

    3.查看所有权限

    @RestController
    @RequestMapping("/managePermission")
    public class ManagePermissionController {
    
        @Autowired
        private ManagePermissionService managePermissionService;
    
        @GetMapping("/getPermissionAll")
        @RequiresPermissions("managePermission:list")
        public List<ManagePermission>  RequiresPermissions(){return managePermissionService.selectList(null);}
    }
    View Code

    4.查看所有人员

    @RestController
    @RequestMapping("/manageUser")
    public class ManageUserController {
    
        @Autowired
        private ManageUserService manageUserService;
    
        @GetMapping("/getUserAll")
        @RequiresPermissions("manageUser:list")
        public List<ManageUser> getUserAll(){return manageUserService.selectList(null);}
    }
    View Code

    八、权限不足全局异常

    @ResponseBody
    @ControllerAdvice
    public class GlobalException {
        //授权异常
        @ExceptionHandler({AuthorizationException.class})
        public Object unauthorizedException() {
            Map<String, Object> map = new HashMap<String, Object>();
            map.put("msg", "权限不足!");
            System.err.println("权限不足");
            return map;
        }
    }
    View Code

    九、Html

    1.登录

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>login</title>
    </head>
    <body>
    <form action="/login" method="POST">
        <input type="text" name="username" id=""></br>
        <input type="password" name="password" id=""></br>
        <input type ="checkbox" name ="rememberMe" value="true" />记住我
        <input type="submit" value="登录">
    </form>
    </body>
    </html>
    loginPage.html

    2.登录成功

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>首页</title>
    </head>
    <body>
        <h1>菜单</h1>
        <div th:each="menu:${menuList}">
            <a th:href="${menu.url}" ><h4 th:text="${menu.name}"></h4></a>
            <div th:each="child:${menu.childrens}" style="margin-left: 50px">
              <a th:href="${child.url}" > <h4 th:text="${child.name}"></h4></a>
            </div>
        </div>
        </br>
        <a href="/logout">退出登录</a>
    </body>
    </html>
    index.html

    3.错误页面

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>错误页面</title>
    </head>
    <body>
    <h2 th:text="${code}" style="color: red"></h2>
    <h1 th:text="${msg}" ></h1>
    <h3 th:text="${data}"></h3>
    </body>
    </html>
    err.html

    项目结构

    本章没有过多的赘述 entity、mapper、service层,可参考【SpringBoot整合Mybatis-Plus

  • 相关阅读:
    能成大事儿的人,都具备这5个特质
    元气森林唐彬森:苦了10年我发现,发大财首先要会选
    反者道之动,亿万富翁查理芒格受用一生的逆向思维
    解决不了bug先放着,这里有40条提升编程技能小妙招
    理解maven命令package、install、deploy的联系与区别
    每日一则
    《穷查理年鉴》贪嗔痴 & 懒贪装(关于败坏)
    C++构造函数
    C++类的定义和封装
    C++访问控制限定符
  • 原文地址:https://www.cnblogs.com/angel-devil/p/11955016.html
Copyright © 2011-2022 走看看