spring boot 整合shiro和redis实现权限管理和登录功能

目的

Shiro这个Java安全框架我一直都想学会怎么去使用,但每次依照着别人的博客尝试把它配置到自己的项目中,总是出现各种问题,导致一直没有成功。经过不懈努力,这一次终于成功了!从零搭建整个项目,并通过一个简单的用户登录功能来进行说明!

环境

1.jdk1.8 / tomcat7、
2.HTML / CSS / JavaScript / thymeleaf / layui
3.shiro / springboot / mybatis-plus / mysql5.7

1. 项目文件结构

spring boot 整合shiro和redis实现权限管理和登录功能_第1张图片
在这里插入图片描述spring boot 整合shiro和redis实现权限管理和登录功能_第2张图片

2.项目搭建

1. application.yml 配置

application.yml

spring:
  profiles:
    active: dev  #yml切换

  thymeleaf:
    cache: false
    prefix: classpath:/templates/
    suffix: .html
    servlet:
      content-type: text/html

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss  #指定日期格式,比如yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8 #指定时区

  aop:
    proxy-target-class: true  #基于类的代理将起作用(这时需要cglib库)

mybatis-plus:
  # 实体类扫描路径
  type-aliases-package: com.liuhu.rainbow.system.entity
  # xml 扫描路径
  mapper-locations: classpath:mapper/*/*.xml
  configuration:
    jdbc-type-for-null: null
  global-config:
    # 关闭 mybatis-plus的 banner
    banner: false

rainbow:
  version: v1.0

server:
  port: 8081
  tomcat:
    uri-encoding: UTF-8

application-dev.yml hikari类似与druid可以忽略 用普通的datasource方式连接就OK

spring:
  datasource:
    dynamic:
      # 是否开启 SQL日志输出,生产环境建议关闭,有性能损耗
      p6spy: true
      hikari:
        connection-timeout: 30000
        max-lifetime: 1800000
        max-pool-size: 15
        min-idle: 5
        connection-test-query: select 1
        pool-name: FebsHikariCP
      # 配置默认数据源
      primary: base
      datasource:
        # 数据源-1,名称为 base
        base:
          username: root
          password: root
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://129.211.60.109:3306/febs_base?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=GMT%2b8
  
  redis:
    # Redis数据库索引(默认为 0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis 密码
    password: root
    jedis:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 8
        # 连接池中的最大空闲连接
        max-idle: 500
        # 连接池最大连接数(使用负值表示没有限制)
        max-active: 2000
        # 连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: 10000
    # 连接超时时间(毫秒)
    timeout: 0

2. pom.xml



    4.0.0
    com.liuhu
    rainbow
    0.0.1-SNAPSHOT
    rainbow
    Demo project for Spring Boot

    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    

    
        1.8
        UTF-8
        UTF-8
        3.1.1
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            com.baomidou
            mybatis-plus-boot-starter
            ${mybatis.plus.version}
        
        
        
            com.baomidou
            mybatis-plus-generator
            ${mybatis.plus.version}
        
          
        
            com.baomidou
            dynamic-datasource-spring-boot-starter
            2.5.4
        
     
        
            org.springframework.boot
            spring-boot-starter-aop
        
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
        
        
            org.springframework.boot
            spring-boot-starter-cache
        
        
        
            org.freemarker
            freemarker
            2.3.28
        

        
        
            p6spy
            p6spy
            3.8.1
        
        
        
            mysql
            mysql-connector-java
            runtime
        

        
        
            org.apache.shiro
            shiro-spring
            1.4.0
        
        
            org.crazycake
            shiro-redis
            3.2.2
        
        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        
        
        
            org.apache.commons
            commons-lang3
        
        
            commons-io
            commons-io
            2.6
        

        
        
            com.google.guava
            guava
            27.0-jre
        
        
        
            org.projectlombok
            lombok
            true
        

        
        
            com.alibaba
            fastjson
            1.2.44
        

        
        
            com.github.whvcse
            EasyCaptcha
            1.5.0
        
    
    
        
            jitpack.io
            https://jitpack.io
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


2. 实体

Menu 资源菜单实体

package com.liuhu.rainbow.system.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;

/**
 * 菜单表
 * @author melo、lh
 * @createTime 2019-07-03 17:37:58
 */

@Data
@TableName("t_menu")
public class Menu implements Serializable {

    private static final long serialVersionUID = 8571011372410167901L;

    // 菜单
    public static final String TYPE_MENU = "0";
    // 按钮
    public static final String TYPE_BUTTON = "1";

    public static final Long TOP_NODE = 0L;

    /**
     * 菜单/按钮ID
     */
    @TableId(value = "MENU_ID", type = IdType.AUTO)
    private Long menuId;

    /**
     * 上级菜单ID
     */
    @TableField("PARENT_ID")
    private Long parentId;

    /**
     * 菜单/按钮名称
     */
    @TableField("MENU_NAME")
    @NotBlank(message = "{required}")
    private String menuName;

    /**
     * 菜单URL
     */
    @TableField("URL")
    private String url;

    /**
     * 权限标识
     */
    @TableField("PERMS")
    private String perms;

    /**
     * 图标
     */
    @TableField("ICON")
    private String icon;

    /**
     * 类型 0菜单 1按钮
     */
    @TableField("TYPE")
    private String type;

    /**
     * 排序
     */
    @TableField("ORDER_NUM")
    private Long orderNum;

    /**
     * 创建时间
     */
    @TableField("CREATE_TIME")
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField("MODIFY_TIME")
    private Date modifyTime;


}

Role 角色实体

package com.liuhu.rainbow.system.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;

/**
 * 角色表
 * @author melo、lh
 * @createTime 2019-07-03 17:35:21
 */

@Data
@TableName("t_role")

public class Role implements Serializable {

    private static final long serialVersionUID = -4493960686192269860L;
    /**
     * 角色ID
     */
    @TableId(value = "ROLE_ID", type = IdType.AUTO)
    private Long roleId;

    /**
     * 角色名称
     */
    @TableField("ROLE_NAME")
    private String roleName;

    /**
     * 角色描述
     */
    @TableField("REMARK")
    private String remark;

    /**
     * 创建时间
     */
    @TableField("CREATE_TIME")
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField("MODIFY_TIME")
    private Date modifyTime;

    /**
     * 角色对应的菜单(按钮) id
     */
    private transient String menuIds;
}

RoleMenu 角色资源关联

package com.liuhu.rainbow.system.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 角色菜单
 * @author melo、lh
 * @createTime 2019-07-03 17:37:58
 */

@Data
@TableName("t_role_menu")
public class RoleMenu implements Serializable {

    private static final long serialVersionUID = -5200596408874170216L;
    /**
     * 角色ID
     */
    @TableField("ROLE_ID")
    private Long roleId;

    /**
     * 菜单/按钮ID
     */
    @TableField("MENU_ID")
    private Long menuId;


}

User 用户

package com.liuhu.rainbow.system.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;
import java.util.Date;

/**
 * 用户表
 * @author melo、lh
 * @createTime 2019-07-05 17:00:55
 */

@Data
@TableName("t_user")
public class User implements Serializable {

    private static final long serialVersionUID = -4352868070794165001L;

    // 用户状态:有效
    public static final String STATUS_VALID = "1";
    // 用户状态:锁定
    public static final String STATUS_LOCK = "0";
    // 默认头像
    public static final String DEFAULT_AVATAR = "default.jpg";
    // 默认密码
    public static final String DEFAULT_PASSWORD = "1234qwer";
    // 性别男
    public static final String SEX_MALE = "0";
    // 性别女
    public static final String SEX_FEMALE = "1";
    // 性别保密
    public static final String SEX_UNKNOW = "2";
    // 黑色主题
    public static final String THEME_BLACK = "black";
    // 白色主题
    public static final String THEME_WHITE = "white";
    // TAB开启
    public static final String TAB_OPEN = "1";
    // TAB关闭
    public static final String TAB_CLOSE = "0";


    /**
     * 用户 ID
     */
    @TableId(value = "USER_ID", type = IdType.AUTO)
    private Long userId;

    /**
     * 用户名
     */
    @TableField("USERNAME")
    private String username;

    /**
     * 密码
     */
    @TableField("PASSWORD")
    private String password;

    /**
     * 部门 ID
     */
    @TableField("DEPT_ID")
    private Long deptId;

    /**
     * 邮箱
     */
    @TableField("EMAIL")
    private String email;

    /**
     * 联系电话
     */
    @TableField("MOBILE")
    private String mobile;

    /**
     * 状态 0锁定 1有效
     */
    @TableField("STATUS")
    @NotBlank(message = "{required}")
    private String status;

    /**
     * 创建时间
     */
    @TableField("CREATE_TIME")
    private Date createTime;

    /**
     * 修改时间
     */
    @TableField("MODIFY_TIME")
    private Date modifyTime;

    /**
     * 最近访问时间
     */
    @TableField("LAST_LOGIN_TIME")
    @JsonFormat(pattern = "yyyy年MM月dd日 HH时mm分ss秒", timezone = "GMT+8")
    private Date lastLoginTime;

    /**
     * 性别 0男 1女 2 保密
     */
    @TableField("SSEX")
    private String sex;

    /**
     * 头像
     */
    @TableField("AVATAR")
    private String avatar;

    /**
     * 主题
     */
    @TableField("THEME")
    private String theme;

    /**
     * 是否开启 tab 0开启,1关闭
     */
    @TableField("IS_TAB")
    private String isTab;

    /**
     * 描述
     */
    @TableField("DESCRIPTION")
    private String description;

    /**
     * 部门名称
     */
    @TableField(exist = false)
    private String deptName;

    @TableField(exist = false)
    private String createTimeFrom;

    @TableField(exist = false)
    private String createTimeTo;
    /**
     * 角色 ID
     */
    @TableField(exist = false)
    private String roleId;

    @TableField(exist = false)
    private String roleName;

    public Long getId() {
        return userId;
    }
}

UserRole 用户角色

package com.liuhu.rainbow.system.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

/**
 * 角色用户表
 * @author melo、lh
 * @createTime 2019-07-05 17:01:05
 */

@Data
@TableName("t_user_role")
public class UserRole implements Serializable {

    private static final long serialVersionUID = 2354394771912648574L;
    /**
     * 用户ID
     */
    @TableField("USER_ID")
    private Long userId;

    /**
     * 角色ID
     */
    @TableField("ROLE_ID")
    private Long roleId;


}

3. 接口和实现类

1. 接口

IUserService
package com.liuhu.rainbow.system.service;

import com.liuhu.rainbow.system.entity.User;

/**
 * 用户业务层接口
 * @author melo、lh
 * @createTime 2019-07-05 17:33:11
 */

public interface IUserService {

    /**
     * 通过用户名查找用户
     * @param username
     * @return com.liuhu.rainbow.system.entity.User
     * @author melo、lh
     * @createTime 2019-07-04 08:56:35
     */
    User findByName(String username);


}

IRoleService
package com.liuhu.rainbow.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.liuhu.rainbow.system.entity.Role;

import java.util.List;

/**
 * 角色业务层接口
 * @author melo、lh
 * @createTime 2019-07-05 17:33:11
 */

public interface IRoleService {

    /**
     * 通过用户名查找用户角色
     * @param username 用户名
     * @return java.util.List
     * @author melo、lh
     * @createTime 2019-07-04 09:14:17
     */
    List findUserRole(String username);



}

IMenuService
package com.liuhu.rainbow.system.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.system.entity.Menu;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 权限业务层接口
 * @author melo、lh
 * @createTime 2019-07-04 09:30:34
 */

public interface IMenuService  {

    /**
     * 查找用户权限集
     * @param username
     * @return java.util.List
     * @author melo、lh
     * @createTime 2019-07-04 09:30:18
     */
    List findUserPermissions( String username);
    /**
     * 获得当前用户的所属菜单
     * @param username
     * @return com.liuhu.rainbow.common.entity.MenuTree
     * @author melo、lh
     * @createTime 2019-07-05 14:22:07
     */
    MenuTree findUserMenus(String username);
}

2.实现类

UserServiceImpl
package com.liuhu.rainbow.system.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liuhu.rainbow.system.entity.User;
import com.liuhu.rainbow.system.mapper.UserMapper;
import com.liuhu.rainbow.system.service.IUserRoleService;
import com.liuhu.rainbow.system.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 用户业务层实现类
 * @author melo、lh
 * @createTime 2019-07-05 17:32:27
 */

@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class UserServiceImpl  implements IUserService {

    @Autowired
    private UserMapper userMapper;


    @Override
    public User findByName(String username) {
        return userMapper.findByName(username);
    }
}

RoleServiceImpl
package com.liuhu.rainbow.system.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.liuhu.rainbow.system.entity.Role;
import com.liuhu.rainbow.system.mapper.RoleMapper;
import com.liuhu.rainbow.system.service.IRoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
 * 角色业务层实现类
 * @author melo、lh
 * @createTime 2019-07-05 17:30:01
 */

@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class RoleServiceImpl  implements IRoleService {

    @Autowired
    private RoleMapper roleMapper;

    @Override
    public List findUserRole(String username) {
        return roleMapper.findUserRole(username);
    }


}

MenuServiceImpl
package com.liuhu.rainbow.system.service.impl;

import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.common.util.TreeUtil;
import com.liuhu.rainbow.system.entity.Menu;
import com.liuhu.rainbow.system.mapper.MenuMapper;
import com.liuhu.rainbow.system.service.IMenuService;
import com.sun.xml.internal.bind.v2.TODO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;

/**
 * 资源业务层实现类
 * @author melo、lh
 * @createTime 2019-07-04 09:32:55
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = Exception.class)
public class MenuServiceImpl implements IMenuService {

    @Autowired
    private MenuMapper menuMapper;
    @Override
    public List findUserPermissions(String username) {
        return menuMapper.findUserPermissions(username);
    }

}

4.Dao层全部为接口

package com.liuhu.rainbow.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuhu.rainbow.system.entity.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 用户持久层
 * @author melo、lh
 * @createTime 2019-07-04 09:00:08
 */
@Repository
public interface UserMapper extends BaseMapper {

    /**
     * 通过用户名查找用户
     * @param username
     * @return com.liuhu.rainbow.system.entity.User
     * @author melo、lh
     * @createTime 2019-07-04 08:56:35
     */
    User findByName(@Param("username") String username);

}

package com.liuhu.rainbow.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.liuhu.rainbow.system.entity.Role;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 角色持久层
 * @author melo、lh
 * @createTime 2019-07-04 09:14:44
 */

@Repository
public interface RoleMapper extends BaseMapper {

    /**
     * 通过用户名查找用户角色
     * @param username 用户名
     * @return java.util.List
     * @author melo、lh
     * @createTime 2019-07-04 09:14:17
     */
    List findUserRole(@Param("username") String username);


}

package com.liuhu.rainbow.system.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.liuhu.rainbow.common.entity.MenuTree;
import com.liuhu.rainbow.system.entity.Menu;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 资源持久层
 * @author melo、lh
 * @createTime 2019-07-04 09:33:51
 */
@Repository
public interface MenuMapper extends BaseMapper {
    /**
     * 查找用户权限集
     * @param username
     * @return java.util.List
     * @author melo、lh
     * @createTime 2019-07-04 09:30:18
     */
    List findUserPermissions(@Param("username")String username);
    /**
     * 查找用户权限集
     * @param username
     * @return java.util.List
     * @author melo、lh
     * @createTime 2019-07-04 09:30:18
     */
    List findUserMenus(@Param("username") String username);
}

5.mapper.xml





    





    
        
        
        
        
        
    

    







    
        
        
        
        
        
        
        
        
        
        
    

    

    


6.整合shiro

ShiroConfig

package com.liuhu.rainbow.common.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.liuhu.rainbow.common.properties.RainbowProperties;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.SessionListener;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.util.Base64Utils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;

/**
 * Shiro 配置类
 *
 * @author MrBird
 */
@Configuration
public class ShiroConfig {

    @Autowired
    private RainbowProperties rainbowProperties;

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.database:0}")
    private int database;


    /**
     * shiro 中配置 redis 缓存
     * @return org.crazycake.shiro.RedisManager
     * @author melo、lh
     * @createTime 2019-07-05 16:48:34
     */
    private RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host + ":" + port);
        if (StringUtils.isNotBlank(password)){
            redisManager.setPassword(password);
        }
        redisManager.setTimeout(timeout);
        redisManager.setDatabase(database);
        return redisManager;
    }
    /**
     * 缓存管理
     * @return org.crazycake.shiro.RedisManager
     * @author melo、lh
     * @createTime 2019-07-05 16:48:34
     */
    private RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }
    /**
     * shiro请求过滤处理
     * @param securityManager
     * @return org.apache.shiro.spring.web.ShiroFilterFactoryBean
     * @author melo、lh
     * @createTime 2019-07-05 16:49:14
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 设置 securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的 url
        shiroFilterFactoryBean.setLoginUrl(rainbowProperties.getShiro().getLoginUrl());
        // 登录成功后跳转的 url
        shiroFilterFactoryBean.setSuccessUrl(rainbowProperties.getShiro().getSuccessUrl());
        // 未授权 url
        shiroFilterFactoryBean.setUnauthorizedUrl(rainbowProperties.getShiro().getUnauthorizedUrl());

        LinkedHashMap filterChainDefinitionMap = new LinkedHashMap<>();
        // 设置免认证 url
        String[] anonUrls = StringUtils.splitByWholeSeparatorPreserveAllTokens(rainbowProperties.getShiro().getAnonUrl(), ",");
        for (String url : anonUrls) {
            filterChainDefinitionMap.put(url, "anon");
        }
        // 配置退出过滤器,其中具体的退出代码 Shiro已经替我们实现了
        filterChainDefinitionMap.put(rainbowProperties.getShiro().getLogoutUrl(), "logout");

        // 除上以外所有 url都必须认证通过才可以访问,未通过认证自动访问 LoginUrl
        filterChainDefinitionMap.put("/**", "user");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    /**
     * 配置安全管理器
     * @param shiroRealm
     * @return org.apache.shiro.mgt.SecurityManager
     * @author melo、lh
     * @createTime 2019-07-05 16:49:54
     */
    @Bean
    public SecurityManager securityManager(ShiroRealm shiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 配置 SecurityManager,并注入 shiroRealm
        securityManager.setRealm(shiroRealm);
        // 配置 shiro session管理器
        securityManager.setSessionManager(sessionManager());
        // 配置 缓存管理类 cacheManager
        securityManager.setCacheManager(cacheManager());
        // 配置 rememberMeCookie
        securityManager.setRememberMeManager(rememberMeManager());
        return securityManager;
    }



    /**
     * rememberMe cookie 效果是重开浏览器后无需重新登录
     * @return org.apache.shiro.web.servlet.SimpleCookie
     * @author melo、lh
     * @createTime 2019-07-05 16:50:14
     */
    private SimpleCookie rememberMeCookie() {
        // 设置 cookie 名称,对应 login.html 页面的 
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        // 设置 cookie 的过期时间,单位为秒,这里为一天
        cookie.setMaxAge(rainbowProperties.getShiro().getCookieTimeout());
        return cookie;
    }

    /**
     * cookie管理对象
     * @return org.apache.shiro.web.mgt.CookieRememberMeManager
     * @author melo、lh
     * @createTime 2019-07-05 16:50:29
     */
    private CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        // rememberMe cookie 加密的密钥
        String encryptKey = "febs_shiro_key";
        byte[] encryptKeyBytes = encryptKey.getBytes(StandardCharsets.UTF_8);
        String rememberKey = Base64Utils.encodeToString(Arrays.copyOf(encryptKeyBytes, 16));
        cookieRememberMeManager.setCipherKey(Base64.decode(rememberKey));
        return cookieRememberMeManager;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 用于开启 Thymeleaf 中的 shiro 标签的使用
     * @return at.pollux.thymeleaf.shiro.dialect.ShiroDialect
     * @author melo、lh
     * @createTime 2019-07-05 16:50:43
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    @Bean
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }


    /**
     * session 管理对象
     * @return org.apache.shiro.web.session.mgt.DefaultWebSessionManager
     * @author melo、lh
     * @createTime 2019-07-05 16:51:05
     */
    @Bean
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        Collection listeners = new ArrayList<>();
        listeners.add(new ShiroSessionListener());
        // 设置 session超时时间
        sessionManager.setGlobalSessionTimeout(rainbowProperties.getShiro().getSessionTimeout() * 1000L);
        sessionManager.setSessionListeners(listeners);
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        return sessionManager;
    }


}

ShiroRealm

package com.liuhu.rainbow.common.shiro;

import com.liuhu.rainbow.system.entity.Menu;
import com.liuhu.rainbow.system.entity.Role;
import com.liuhu.rainbow.system.entity.User;
import com.liuhu.rainbow.system.service.IMenuService;
import com.liuhu.rainbow.system.service.IRoleService;
import com.liuhu.rainbow.system.service.IUserService;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;
import java.util.Set;


/**
 * 自定义实现 ShiroRealm,包含认证和授权两大模块
 * @author melo、lh
 * @createTime 2019-07-05 16:52:00
 */
@Component
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    private IUserService userService;
    @Autowired
    private IRoleService roleService;
    @Autowired
    private IMenuService menuService;


    /**
     * 授权模块,获取用户角色和权限
     * @param principal
     * @return org.apache.shiro.authz.AuthorizationInfo
     * @author melo、lh
     * @createTime 2019-07-05 16:52:09
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        User user = (User) SecurityUtils.getSubject().getPrincipal();
        String username = user.getUsername();

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 获取用户角色集
        List roleList = this.roleService.findUserRole(username);
        // 角色名称集合
        Set roleSet =  new HashSet<>();
        for (Role role: roleList) {
            roleSet.add(role.getRoleName());
        }
        /*Set roleSet = roleList.stream().map(Role::getRoleName).collect(Collectors.toSet());*/
        info.setRoles(roleSet);

        // 获取用户权限集合
        List permissionList = this.menuService.findUserPermissions(username);
        // 权限集合
        Set permissionSet =  new HashSet<>();
        for (Menu menu: permissionList) {
            permissionSet .add(menu.getPerms());
        }
      /*  Set permissionSet = permissionList.stream().map(Menu::getPerms).collect(Collectors.toSet());*/
        info.setStringPermissions(permissionSet);
        return info;
    }


    /**
     * 用户认证
     * @param token 身份认证
     * @return org.apache.shiro.authc.AuthenticationInfo
     * @author melo、lh
     * @createTime 2019-07-05 16:53:14
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取用户输入的用户名和密码
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

        // 通过用户名到数据库查询用户信息
        User user = this.userService.findByName(username);

        if (user == null){
            throw new UnknownAccountException("用户名或密码错误!");
        }
        if (!StringUtils.equals(password, user.getPassword())) {
            throw new IncorrectCredentialsException("用户名或密码错误!");
        }
        if (User.STATUS_LOCK.equals(user.getStatus())){
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        }

        return new SimpleAuthenticationInfo(user,password,this.getName());
    }


    /**
     * 清除当前用户权限缓存
     * 使用方法:在需要清除用户权限的地方注入 ShiroRealm,
     * 然后调用其 clearCache方法。
     * @return void
     * @author melo、lh
     * @createTime 2019-07-05 16:52:40
     */
    public void clearCache() {
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }
}

redis配置 (我也不是很清楚具体怎么用的)

package cc.mrbird.febs.common.configure;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.IOException;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.Arrays;


@Configuration
public class RedisConfigure extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;
    @Value("${spring.redis.password}")
    private String password;
    @Value("${spring.redis.timeout}")
    private int timeout;
    @Value("${spring.redis.jedis.pool.max-idle}")
    private int maxIdle;
    @Value("${spring.redis.jedis.pool.max-wait}")
    private long maxWaitMillis;
    @Value("${spring.redis.database:0}")
    private int database;

    @Bean
    public JedisPool redisPoolFactory() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        if (StringUtils.isNotBlank(password)) {
            return new JedisPool(jedisPoolConfig, host, port, timeout, password, database);
        } else {
            return new JedisPool(jedisPoolConfig, host, port, timeout, null, database);
        }
    }

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(host);
        redisStandaloneConfiguration.setPort(port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        redisStandaloneConfiguration.setDatabase(database);

        JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfiguration = JedisClientConfiguration.builder();
        jedisClientConfiguration.connectTimeout(Duration.ofMillis(timeout));
        jedisClientConfiguration.usePooling();
        return new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration.build());
    }

    @Bean(name = "redisTemplate")
    @SuppressWarnings({"unchecked", "rawtypes"})
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate<>();

        //使用 fastjson 序列化
        JacksonRedisSerializer jacksonRedisSerializer = new JacksonRedisSerializer(Object.class);
        // value 值的序列化采用 jacksonRedisSerializer
        template.setValueSerializer(jacksonRedisSerializer);
        template.setHashValueSerializer(jacksonRedisSerializer);
        // key 的序列化采用 StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    //缓存管理器
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(redisConnectionFactory);
        return builder.build();
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public KeyGenerator wiselyKeyGenerator() {
        return (target, method, params) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append(method.getName());
            Arrays.stream(params).map(Object::toString).forEach(sb::append);
            return sb.toString();
        };
    }

    @Bean
    public RedisTemplate limitRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}

class JacksonRedisSerializer implements RedisSerializer {
    private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class clazz;
    private ObjectMapper mapper;

    JacksonRedisSerializer(Class clazz) {
        super();
        this.clazz = clazz;
        this.mapper = new ObjectMapper();
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        try {
            return mapper.writeValueAsBytes(t);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes.length <= 0) {
            return null;
        }
        try {
            return mapper.readValue(bytes, clazz);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

package com.liuhu.rainbow.common.function;

import com.liuhu.rainbow.common.exception.RedisConnectException;


/**
 *
 * @author melo、lh
 * @createTime 2019-07-05 16:47:32
 */
@FunctionalInterface
public interface JedisExecutor {
    R excute(T t) throws RedisConnectException;
}

package com.liuhu.rainbow.common.properties;

import lombok.Data;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;

/**
 * 讀取shiro配置文件屬性
 * @author melo、lh
 * @createTime 2019-07-03 17:57:50
 */
@Data
@SpringBootConfiguration
@PropertySource(value = {"classpath:rainbow.properties"})
@ConfigurationProperties(prefix = "rainbow")
public class RainbowProperties {

    private ShiroProperties shiro = new ShiroProperties();
    private boolean openAopLog = true;
}

package com.liuhu.rainbow.common.properties;

import lombok.Data;

/**
 * shiro属性
 * @author melo、lh
 * @createTime 2019-07-05 16:48:09
 */
@Data
public class ShiroProperties {

    private long sessionTimeout;
    private int cookieTimeout;
    private String anonUrl;
    private String loginUrl;
    private String successUrl;
    private String logoutUrl;
    private String unauthorizedUrl;
}

package com.liuhu.rainbow.common.util;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;

/**
 * MD5
 * @author melo、lh
 * @createTime 2019-07-05 16:59:32
 */

public class MD5Util {

    protected MD5Util() {

    }
    /**加密方式*/
    private static final String ALGORITH_NAME = "md5";
    /**加密测试*/
    private static final int HASH_ITERATIONS = 1024;
    /**
     * 通过MD5加密
     * @param username 用户名
     * @param password 密码
     * @return java.lang.String
     * @author melo、lh
     * @createTime 2019-07-05 16:43:41
     */
    public static String encrypt(String username, String password) {
        // 小写的用户名作为盐加密
        String source = StringUtils.lowerCase(username);
        password = StringUtils.lowerCase(password);
        return new SimpleHash(ALGORITH_NAME, password, ByteSource.Util.bytes(source), HASH_ITERATIONS).toHex();
    }

    public static void main(String[] args) {
        String encrypt = encrypt("admin", "admin");
        System.out.println(encrypt);
    }
}

package com.liuhu.rainbow.common.util;

import com.liuhu.rainbow.common.constant.RainbowConstant;
import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.monitor.service.IRedisService;
import com.wf.captcha.Captcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.io.IOException;


/**
 * 验证码工具类
 * @author melo、lh
 * @createTime 2019-07-05 16:57:24
 */
@Slf4j
public class CaptchaUtil {

    private static IRedisService redisService = SpringContextUtil.getBean(IRedisService.class);

    // gif 类型验证码
    private static final int GIF_TYPE = 1;
    // png 类型验证码
    private static final int PNG_TYPE = 0;

    // 验证码图片默认高度
    private static final int DEFAULT_HEIGHT = 48;
    // 验证码图片默认宽度
    private static final int DEFAULT_WIDTH = 130;
    // 验证码默认位数
    private static final int DEFAULT_LEN = 5;

    public static void out(HttpServletRequest request, HttpServletResponse response) throws IOException {
        out(DEFAULT_LEN, request, response);
    }

    public static void out(int len, HttpServletRequest request, HttpServletResponse response) throws IOException {
        out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, request, response);
    }

    public static void out(int len, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
        out(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, font, request, response);
    }

    public static void out(int width, int height, int len, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
        out(width, height, len, vType, null, request, response);
    }

    public static void out(int width, int height, int len, Integer vType, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
        outCaptcha(width, height, len, font, GIF_TYPE, vType, request, response);
    }

    public static void outPng(HttpServletRequest request, HttpServletResponse response) throws IOException {
        outPng(DEFAULT_LEN, request, response);
    }

    public static void outPng(int len, HttpServletRequest request, HttpServletResponse response) throws IOException {
        outPng(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, request, response);
    }

    public static void outPng(int len, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
        outPng(DEFAULT_WIDTH, DEFAULT_HEIGHT, len, null, font, request, response);
    }

    public static void outPng(int width, int height, int len, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
        outPng(width, height, len, vType, null, request, response);
    }

    public static void outPng(int width, int height, int len, Integer vType, Font font, HttpServletRequest request, HttpServletResponse response) throws IOException {
        outCaptcha(width, height, len, font, PNG_TYPE, vType, request, response);
    }

    public static boolean verify(String code, HttpServletRequest request) {
        HttpSession session = request.getSession();
        String key = RainbowConstant.CODE_PREFIX + session.getId();
        String sessionCode = "";
        try {
            sessionCode = redisService.get(key);
        } catch (RedisConnectException e) {
            log.error("获取验证码异常", e);
        }
        return StringUtils.equalsIgnoreCase(code, sessionCode);
    }

    private static void outCaptcha(int width, int height, int len, Font font, int cType, Integer vType, HttpServletRequest request, HttpServletResponse response) throws IOException {
        setHeader(response, cType);
        Captcha captcha = null;
        if (cType == GIF_TYPE) {
            captcha = new GifCaptcha(width, height, len);
        } else {
            captcha = new SpecCaptcha(width, height, len);
        }
        if (font != null) {
            captcha.setFont(font);
        }
        if (vType != null) {
            captcha.setCharType(vType);
        }
        HttpSession session = request.getSession();
        String code = captcha.text().toLowerCase();
        String key = RainbowConstant.CODE_PREFIX + session.getId();

        try {
            redisService.set(key, code, 120000L);
        } catch (RedisConnectException e) {
            log.error("保存验证码异常", e);
        }

        captcha.out(response.getOutputStream());
    }

    public static void setHeader(HttpServletResponse response, int cType) {
        if (cType == GIF_TYPE) {
            response.setContentType("image/gif");
        } else {
            response.setContentType("image/png");
        }
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0L);
    }
}

package com.liuhu.rainbow.common.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Locale;


/**
 * 时间工具类
 * @author melo、lh
 * @createTime 2019-07-05 16:57:45
 */

public class DateUtil {

    public static final String FULL_TIME_PATTERN = "yyyyMMddHHmmss";

    public static final String FULL_TIME_SPLIT_PATTERN = "yyyy-MM-dd HH:mm:ss";

    public static final String CST_TIME_PATTERN = "EEE MMM dd HH:mm:ss zzz yyyy";

    public static String formatFullTime(LocalDateTime localDateTime) {
        return formatFullTime(localDateTime, FULL_TIME_PATTERN);
    }

    public static String formatFullTime(LocalDateTime localDateTime, String pattern) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern);
        return localDateTime.format(dateTimeFormatter);
    }

    public static String getDateFormat(Date date, String dateFormatType) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(dateFormatType, Locale.CHINA);
        return simpleDateFormat.format(date);
    }

    public static String formatCSTTime(String date, String format) throws ParseException {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat(CST_TIME_PATTERN, Locale.US);
        Date usDate = simpleDateFormat.parse(date);
        return DateUtil.getDateFormat(usDate, format);
    }

    public static String formatInstant(Instant instant, String format) {
        LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        return localDateTime.format(DateTimeFormatter.ofPattern(format));
    }
}

package com.liuhu.rainbow.monitor.entity;

import lombok.Data;
import lombok.ToString;

import java.util.HashMap;
import java.util.Map;

/**
 * @author MrBird
 */
@Data
@ToString
public class RedisInfo {

    private static Map map = new HashMap<>();

    static {
        map.put("redis_version", "Redis 服务器版本");
        map.put("redis_git_sha1", "Git SHA1");
        map.put("redis_git_dirty", "Git dirty flag");
        map.put("os", "Redis 服务器的宿主操作系统");
        map.put("arch_bits", " 架构(32 或 64 位)");
        map.put("multiplexing_api", "Redis 所使用的事件处理机制");
        map.put("gcc_version", "编译 Redis 时所使用的 GCC 版本");
        map.put("process_id", "服务器进程的 PID");
        map.put("run_id", "Redis 服务器的随机标识符(用于 Sentinel 和集群)");
        map.put("tcp_port", "TCP/IP 监听端口");
        map.put("uptime_in_seconds", "自 Redis 服务器启动以来,经过的秒数");
        map.put("uptime_in_days", "自 Redis 服务器启动以来,经过的天数");
        map.put("lru_clock", " 以分钟为单位进行自增的时钟,用于 LRU 管理");
        map.put("connected_clients", "已连接客户端的数量(不包括通过从属服务器连接的客户端)");
        map.put("client_longest_output_list", "当前连接的客户端当中,最长的输出列表");
        map.put("client_longest_input_buf", "当前连接的客户端当中,最大输入缓存");
        map.put("blocked_clients", "正在等待阻塞命令(BLPOP、BRPOP、BRPOPLPUSH)的客户端的数量");
        map.put("used_memory", "由 Redis 分配器分配的内存总量,以字节(byte)为单位");
        map.put("used_memory_human", "以人类可读的格式返回 Redis 分配的内存总量");
        map.put("used_memory_rss", "从操作系统的角度,返回 Redis 已分配的内存总量(俗称常驻集大小)。这个值和 top 、 ps 等命令的输出一致");
        map.put("used_memory_peak", " Redis 的内存消耗峰值(以字节为单位)");
        map.put("used_memory_peak_human", "以人类可读的格式返回 Redis 的内存消耗峰值");
        map.put("used_memory_lua", "Lua 引擎所使用的内存大小(以字节为单位)");
        map.put("mem_fragmentation_ratio", "sed_memory_rss 和 used_memory 之间的比率");
        map.put("mem_allocator", "在编译时指定的, Redis 所使用的内存分配器。可以是 libc 、 jemalloc 或者 tcmalloc");

        map.put("redis_build_id", "redis_build_id");
        map.put("redis_mode", "运行模式,单机(standalone)或者集群(cluster)");
        map.put("atomicvar_api", "atomicvar_api");
        map.put("hz", "redis内部调度(进行关闭timeout的客户端,删除过期key等等)频率,程序规定serverCron每秒运行10次。");
        map.put("executable", "server脚本目录");
        map.put("config_file", "配置文件目录");
        map.put("client_biggest_input_buf", "当前连接的客户端当中,最大输入缓存,用client list命令观察qbuf和qbuf-free两个字段最大值");
        map.put("used_memory_rss_human", "以人类可读的方式返回 Redis 已分配的内存总量");
        map.put("used_memory_peak_perc", "内存使用率峰值");
        map.put("total_system_memory", "系统总内存");
        map.put("total_system_memory_human", "以人类可读的方式返回系统总内存");
        map.put("used_memory_lua_human", "以人类可读的方式返回Lua 引擎所使用的内存大小");
        map.put("maxmemory", "最大内存限制,0表示无限制");
        map.put("maxmemory_human", "以人类可读的方式返回最大限制内存");
        map.put("maxmemory_policy", "超过内存限制后的处理策略");
        map.put("loading", "服务器是否正在载入持久化文件");
        map.put("rdb_changes_since_last_save", "离最近一次成功生成rdb文件,写入命令的个数,即有多少个写入命令没有持久化");
        map.put("rdb_bgsave_in_progress", "服务器是否正在创建rdb文件");
        map.put("rdb_last_save_time", "离最近一次成功创建rdb文件的时间戳。当前时间戳 - rdb_last_save_time=多少秒未成功生成rdb文件");
        map.put("rdb_last_bgsave_status", "最近一次rdb持久化是否成功");
        map.put("rdb_last_bgsave_time_sec", "最近一次成功生成rdb文件耗时秒数");
        map.put("rdb_current_bgsave_time_sec", "如果服务器正在创建rdb文件,那么这个域记录的就是当前的创建操作已经耗费的秒数");
        map.put("aof_enabled", "是否开启了aof");
        map.put("aof_rewrite_in_progress", "标识aof的rewrite操作是否在进行中");
        map.put("aof_rewrite_scheduled",
                "rewrite任务计划,当客户端发送bgrewriteaof指令,如果当前rewrite子进程正在执行,那么将客户端请求的bgrewriteaof变为计划任务,待aof子进程结束后执行rewrite ");

        map.put("aof_last_rewrite_time_sec", "最近一次aof rewrite耗费的时长");
        map.put("aof_current_rewrite_time_sec", "如果rewrite操作正在进行,则记录所使用的时间,单位秒");
        map.put("aof_last_bgrewrite_status", "上次bgrewrite aof操作的状态");
        map.put("aof_last_write_status", "上次aof写入状态");

        map.put("total_commands_processed", "redis处理的命令数");
        map.put("total_connections_received", "新创建连接个数,如果新创建连接过多,过度地创建和销毁连接对性能有影响,说明短连接严重或连接池使用有问题,需调研代码的连接设置");
        map.put("instantaneous_ops_per_sec", "redis当前的qps,redis内部较实时的每秒执行的命令数");
        map.put("total_net_input_bytes", "redis网络入口流量字节数");
        map.put("total_net_output_bytes", "redis网络出口流量字节数");

        map.put("instantaneous_input_kbps", "redis网络入口kps");
        map.put("instantaneous_output_kbps", "redis网络出口kps");
        map.put("rejected_connections", "拒绝的连接个数,redis连接个数达到maxclients限制,拒绝新连接的个数");
        map.put("sync_full", "主从完全同步成功次数");

        map.put("sync_partial_ok", "主从部分同步成功次数");
        map.put("sync_partial_err", "主从部分同步失败次数");
        map.put("expired_keys", "运行以来过期的key的数量");
        map.put("evicted_keys", "运行以来剔除(超过了maxmemory后)的key的数量");
        map.put("keyspace_hits", "命中次数");
        map.put("keyspace_misses", "没命中次数");
        map.put("pubsub_channels", "当前使用中的频道数量");
        map.put("pubsub_patterns", "当前使用的模式的数量");
        map.put("latest_fork_usec", "最近一次fork操作阻塞redis进程的耗时数,单位微秒");
        map.put("role", "实例的角色,是master or slave");
        map.put("connected_slaves", "连接的slave实例个数");
        map.put("master_repl_offset", "主从同步偏移量,此值如果和上面的offset相同说明主从一致没延迟");
        map.put("repl_backlog_active", "复制积压缓冲区是否开启");
        map.put("repl_backlog_size", "复制积压缓冲大小");
        map.put("repl_backlog_first_byte_offset", "复制缓冲区里偏移量的大小");
        map.put("repl_backlog_histlen",
                "此值等于 master_repl_offset - repl_backlog_first_byte_offset,该值不会超过repl_backlog_size的大小");
        map.put("used_cpu_sys", "将所有redis主进程在核心态所占用的CPU时求和累计起来");
        map.put("used_cpu_user", "将所有redis主进程在用户态所占用的CPU时求和累计起来");
        map.put("used_cpu_sys_children", "将后台进程在核心态所占用的CPU时求和累计起来");
        map.put("used_cpu_user_children", "将后台进程在用户态所占用的CPU时求和累计起来");
        map.put("cluster_enabled", "实例是否启用集群模式");
        map.put("db0", "db0的key的数量,以及带有生存期的key的数,平均存活时间");

    }

    private String key;
    private String value;
    private String description;

    public void setKey(String key) {
        this.key = key;
        this.description = map.get(this.key);
    }
}

package com.liuhu.rainbow.monitor.service;

import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.monitor.entity.RedisInfo;

import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * redis工具类接口
 * @author melo、lh
 * @createTime 2019-07-05 17:00:00
 */

public interface IRedisService {

    /**
     * 获取 redis 的详细信息
     *
     * @return List
     */
    List getRedisInfo() throws RedisConnectException;

    /**
     * 获取 redis key 数量
     *
     * @return Map
     */
    Map getKeysSize() throws RedisConnectException;

    /**
     * 获取 redis 内存信息
     *
     * @return Map
     */
    Map getMemoryInfo() throws RedisConnectException;

    /**
     * 获取 key
     *
     * @param pattern 正则
     * @return Set
     */
    Set getKeys(String pattern) throws RedisConnectException;

    /**
     * get命令
     *
     * @param key key
     * @return String
     */
    String get(String key) throws RedisConnectException;

    /**
     * set命令
     *
     * @param key   key
     * @param value value
     * @return String
     */
    String set(String key, String value) throws RedisConnectException;

    /**
     * set 命令
     *
     * @param key         key
     * @param value       value
     * @param milliscends 毫秒
     * @return String
     */
    String set(String key, String value, Long milliscends) throws RedisConnectException;

    /**
     * del命令
     *
     * @param key key
     * @return Long
     */
    Long del(String... key) throws RedisConnectException;

    /**
     * exists命令
     *
     * @param key key
     * @return Boolean
     */
    Boolean exists(String key) throws RedisConnectException;

    /**
     * pttl命令
     *
     * @param key key
     * @return Long
     */
    Long pttl(String key) throws RedisConnectException;

    /**
     * pexpire命令
     *
     * @param key         key
     * @param milliscends 毫秒
     * @return Long
     */
    Long pexpire(String key, Long milliscends) throws RedisConnectException;


    /**
     * zadd 命令
     *
     * @param key    key
     * @param score  score
     * @param member value
     */
    Long zadd(String key, Double score, String member) throws RedisConnectException;

    /**
     * zrangeByScore 命令
     *
     * @param key key
     * @param min min
     * @param max max
     * @return Set
     */
    Set zrangeByScore(String key, String min, String max) throws RedisConnectException;

    /**
     * zremrangeByScore 命令
     *
     * @param key   key
     * @param start start
     * @param end   end
     * @return Long
     */
    Long zremrangeByScore(String key, String start, String end) throws RedisConnectException;

    /**
     * zrem 命令
     *
     * @param key     key
     * @param members members
     * @return Long
     */
    Long zrem(String key, String... members) throws RedisConnectException;
}

package com.liuhu.rainbow.monitor.service.impl;


import com.liuhu.rainbow.common.exception.RedisConnectException;
import com.liuhu.rainbow.common.function.JedisExecutor;
import com.liuhu.rainbow.monitor.entity.RedisInfo;
import com.liuhu.rainbow.monitor.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Client;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.*;


/**
 * Redis 工具类,只封装了几个常用的 redis 命令,可根据实际需要按类似的方式扩展即可。
 * @author melo、lh
 * @createTime 2019-07-05 16:58:52
 */

@Component
public class RedisServiceImpl implements IRedisService {

    @Autowired
    JedisPool jedisPool;

    private static String separator = System.getProperty("line.separator");

    /**
     * 处理 jedis请求
     *
     * @param j 处理逻辑,通过 lambda行为参数化
     * @return 处理结果
     */
    private  T excuteByJedis(JedisExecutor j) throws RedisConnectException {
        try (Jedis jedis = jedisPool.getResource()) {
            return j.excute(jedis);
        } catch (Exception e) {
            throw new RedisConnectException(e.getMessage());
        }
    }

    @Override
    public List getRedisInfo() throws RedisConnectException {
        String info = this.excuteByJedis(
                j -> {
                    Client client = j.getClient();
                    client.info();
                    return client.getBulkReply();
                }
        );
        List infoList = new ArrayList<>();
        String[] strs = Objects.requireNonNull(info).split(separator);
        RedisInfo redisInfo;
        if (strs.length > 0) {
            for (String str1 : strs) {
                redisInfo = new RedisInfo();
                String[] str = str1.split(":");
                if (str.length > 1) {
                    String key = str[0];
                    String value = str[1];
                    redisInfo.setKey(key);
                    redisInfo.setValue(value);
                    infoList.add(redisInfo);
                }
            }
        }
        return infoList;
    }

    @Override
    public Map getKeysSize() throws RedisConnectException {
        Long dbSize = this.excuteByJedis(
                j -> {
                    Client client = j.getClient();
                    client.dbSize();
                    return client.getIntegerReply();
                }
        );
        Map map = new HashMap<>();
        map.put("dbSize", dbSize);
        return map;
    }

    @Override
    public Map getMemoryInfo() throws RedisConnectException {
        String info = this.excuteByJedis(
                j -> {
                    Client client = j.getClient();
                    client.info();
                    return client.getBulkReply();
                }
        );
        String[] strs = Objects.requireNonNull(info).split(separator);
        Map map = null;
        for (String s : strs) {
            String[] detail = s.split(":");
            if ("used_memory".equals(detail[0])) {
                map = new HashMap<>();
                map.put("used_memory", detail[1].substring(0, detail[1].length() - 1));
                break;
            }
        }
        return map;
    }

    @Override
    public Set getKeys(String pattern) throws RedisConnectException {
        return this.excuteByJedis(j -> j.keys(pattern));
    }

    @Override
    public String get(String key) throws RedisConnectException {
        return this.excuteByJedis(j -> j.get(key.toLowerCase()));
    }

    @Override
    public String set(String key, String value) throws RedisConnectException {
        return this.excuteByJedis(j -> j.set(key.toLowerCase(), value));
    }

    @Override
    public String set(String key, String value, Long milliscends) throws RedisConnectException {
        String result = this.set(key.toLowerCase(), value);
        this.pexpire(key, milliscends);
        return result;
    }

    @Override
    public Long del(String... key) throws RedisConnectException {
        return this.excuteByJedis(j -> j.del(key));
    }

    @Override
    public Boolean exists(String key) throws RedisConnectException {
        return this.excuteByJedis(j -> j.exists(key));
    }

    @Override
    public Long pttl(String key) throws RedisConnectException {
        return this.excuteByJedis(j -> j.pttl(key));
    }

    @Override
    public Long pexpire(String key, Long milliseconds) throws RedisConnectException {
        return this.excuteByJedis(j -> j.pexpire(key, milliseconds));
    }

    @Override
    public Long zadd(String key, Double score, String member) throws RedisConnectException {
        return this.excuteByJedis(j -> j.zadd(key, score, member));
    }

    @Override
    public Set zrangeByScore(String key, String min, String max) throws RedisConnectException {
        return this.excuteByJedis(j -> j.zrangeByScore(key, min, max));
    }

    @Override
    public Long zremrangeByScore(String key, String start, String end) throws RedisConnectException {
        return this.excuteByJedis(j -> j.zremrangeByScore(key, start, end));
    }

    @Override
    public Long zrem(String key, String... members) throws RedisConnectException {
        return this.excuteByJedis(j -> j.zrem(key, members));
    }

}

package com.liuhu.rainbow.common.util;

import com.liuhu.rainbow.common.constant.RainbowConstant;

import javax.servlet.http.HttpServletRequest;

/**
 * 系统工具类
 * @author melo、lh
 * @createTime 2019-07-04 14:06:18
 */
public class RainbowUtil {
   /**
    * 返回视图加前缀
    * @param viewName 视图名称
    * @return java.lang.String
    * @author melo、lh
    * @createTime 2019-07-04 14:06:33
    */
    public static String view(String viewName){
        return RainbowConstant.VIEW_PREFIX+viewName;
    }

    /**
     * 判断是否为 ajax请求
     * @param request
     * @return boolean
     * @author melo、lh
     * @createTime 2019-07-04 14:42:51
     */
    public static boolean isAjaxRequest(HttpServletRequest request) {
        return (request.getHeader("X-Requested-With") != null
                && "XMLHttpRequest".equals(request.getHeader("X-Requested-With")));
    }

}

7. 自定义异常

package com.liuhu.rainbow.common.exception;
/**
 * 自定义异常
 * @author melo、lh
 * @createTime 2019-07-04 11:25:26
 */

public class RainbowException extends  Exception{
    public RainbowException(String message) {
        super(message);
    }
}

package com.liuhu.rainbow.common.exception;

/**
 * Redis 连接异常
 * @author melo、lh
 * @createTime 2019-07-05 16:47:22
 */
public class RedisConnectException extends Exception {

    private static final long serialVersionUID = 1639374111871115063L;

    public RedisConnectException(String message) {
        super(message);
    }
}

package com.liuhu.rainbow.common.entity;

import org.springframework.http.HttpStatus;

import java.util.HashMap;

/**
 * 响应类
 * @author melo、lh
 * @createTime 2019-07-05 15:24:16
 */
public class RainbowResponse extends HashMap {

    private static final long serialVersionUID = -8713837118340960775L;

    public RainbowResponse code(HttpStatus status) {
        this.put("code", status.value());
        return this;
    }

    public RainbowResponse message(String message) {
        this.put("message", message);
        return this;
    }

    public RainbowResponse data(Object data) {
        this.put("data", data);
        return this;
    }

    public RainbowResponse success() {
        this.code(HttpStatus.OK);
        return this;
    }

    public RainbowResponse fail() {
        this.code(HttpStatus.INTERNAL_SERVER_ERROR);
        return this;
    }

    @Override
    public RainbowResponse put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}

8. LoginController 登录

package com.liuhu.rainbow.system.controller;

import com.liuhu.rainbow.common.entity.RainbowResponse;
import com.liuhu.rainbow.common.exception.RainbowException;
import com.liuhu.rainbow.common.util.CaptchaUtil;
import com.liuhu.rainbow.common.util.MD5Util;
import com.liuhu.rainbow.common.util.RainbowUtil;
import com.liuhu.rainbow.system.service.IUserService;
import com.wf.captcha.Captcha;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotBlank;

/**
 * 登陆控制层
 * @author melo、lh
 * @createTime 2019-07-05 17:00:02
 */

@Validated
@RestController
public class LoginController {

    @Autowired
    private IUserService userService;

    @PostMapping("login")
    public RainbowResponse login(
            @NotBlank(message = "{required}") String username,
            @NotBlank(message = "{required}") String password,
            @NotBlank(message = "{required}") String verifyCode,
            boolean rememberMe, HttpServletRequest request) throws RainbowException {
        if (!CaptchaUtil.verify(verifyCode, request)) {
            throw new RainbowException("验证码错误!");
        }
        // 加密
        password = MD5Util.encrypt(username.toLowerCase(), password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return new RainbowResponse().success();
        } catch (UnknownAccountException | IncorrectCredentialsException | LockedAccountException e) {
            throw new RainbowException(e.getMessage());
        } catch (AuthenticationException e) {
            throw new RainbowException("认证失败!");
        }
    }


    @GetMapping("images/captcha")
    public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
        CaptchaUtil.outPng(110, 34, 4, Captcha.TYPE_ONLY_NUMBER, request, response);
    }
}

### 9. 登录页面

FEBS 权限系统
``` ### 9. 最终效果

spring boot 整合shiro和redis实现权限管理和登录功能_第3张图片https://github.com/makePromise/rainbow.git 附上源码

你可能感兴趣的:(springboot)