第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)

第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)

第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)_第1张图片

介绍

随着企业级应用程序的复杂性不断增加,安全性成为开发过程中至关重要的一环。Spring Boot作为一种流行的Java开发框架,为开发人员提供了快速构建应用程序的便利性,而Shiro安全框架则为应用程序的安全性提供了可靠的解决方案。本技术文档将详细介绍如何将这两个强大的框架整合在一起,以构建安全可靠的企业级应用程序。

在本文中,我们将深入探讨如何使用Spring Boot和Shiro框架来实现认证、授权和安全策略的配置。我们还将分享一些优化技巧和最佳实践,以确保整合后的应用程序能够在安全性和性能方面达到最佳状态。此外,我们还将提供完整的项目代码,以便读者可以直接参考并应用于实际项目中。

无论您是初学者还是有经验的开发人员,本文都将为您提供全面的指导,帮助您轻松地将Spring Boot和Shiro安全框架整合到您的企业级应用程序中。

让我们一起深入探讨如何实现这一整合,并构建安全可靠的企业级应用程序!

项目地址:Spring Boot 整合 Shiro 安全框架
目录结构:
第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)_第2张图片

Shiro 的 Web 流程

第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)_第3张图片

  • 用户请求:用户向Web应用程序发出请求,例如访问某个页面或执行某个操作。

  • 过滤器链:Shiro通过一系列的过滤器来处理用户请求。这些过滤器可以用于认证、授权、会话管理等操作。过滤器链的配置可以在Shiro的配置文件中进行定义。

  • 认证:如果用户请求需要进行认证,Shiro将会检查用户是否已经登录。如果用户未登录,Shiro将会跳转到登录页面或者返回未认证的错误信息。

  • 登录:用户在登录页面输入用户名和密码,提交登录请求。

  • 认证处理:Shiro将会对用户提交的登录信息进行认证处理,例如验证用户名和密码是否匹配。

  • 认证成功:如果认证成功,Shiro将会创建一个表示用户身份的Principal,并将其存储在会话中。

  • 授权:在用户认证成功后,Shiro将会检查用户是否有权限执行所请求的操作。如果用户没有足够的权限,Shiro将会返回未授权的错误信息或者跳转到未授权页面。

  • 执行操作:如果用户通过了认证和授权,Shiro将会允许用户执行所请求的操作,例如访问页面或者执行特定的功能。

  • 会话管理:Shiro还提供了会话管理功能,用于跟踪用户的会话状态,例如会话超时、会话失效等。

  • 响应:Web应用程序将会根据用户的请求和Shiro的处理结果生成相应的响应,返回给用户。

权限数据库表设计

在数据库设计中,经典的五张表通常包括用户表、角色表、权限表、用户角色关联表和角色权限关联表。这些表通常用于实现用户身份认证和权限控制的功能。
第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)_第4张图片

  • 用户表:用于存储系统中的用户信息,包括用户ID、用户名、密码、邮箱、电话号码等。用户表是系统中的核心表之一,用于存储用户的基本信息。

  • 角色表:用于存储系统中的角色信息,包括角色ID、角色名称、角色描述等。角色表用于定义系统中的角色,例如管理员、普通用户、编辑等。

  • 权限表:用于存储系统中的权限信息,包括权限ID、权限名称、权限描述、权限类型等。权限表用于定义系统中的各种权限,例如访问某个页面、执行某个操作等。

  • 用户角色关联表:用于建立用户和角色之间的关联关系。这张表通常包括用户ID和角色ID两个字段,用于表示某个用户具有哪些角色。

  • 角色权限关联表:用于建立角色和权限之间的关联关系。这张表通常包括角色ID和权限ID两个字段,用于表示某个角色具有哪些权限。

通过合理设计和使用这五张表,可以实现用户身份认证和权限控制的功能,为系统提供安全可靠的访问控制机制。同时,这些表也为系统的扩展和维护提供了良好的基础。

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `perm_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '超级管理员');
INSERT INTO `role` VALUES (2, '运营管理员');
INSERT INTO `role` VALUES (3, '商品管理员');

-- ----------------------------
-- Table structure for role_perm
-- ----------------------------
DROP TABLE IF EXISTS `role_perm`;
CREATE TABLE `role_perm`  (
  `rid` bigint NULL DEFAULT NULL,
  `pid` bigint NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_perm
-- ----------------------------

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `salt` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '3e48d56a6fc20c50f589f562cdf20451', 'abcdefg123456');
INSERT INTO `user` VALUES (2, 'test', '2c4529400f6d307733040330b0f8df7d', 'abcdefg123456');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `uid` bigint NULL DEFAULT NULL,
  `rid` bigint NULL DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1);
INSERT INTO `user_role` VALUES (1, 2);

SET FOREIGN_KEY_CHECKS = 1;

Spring Boot 整合 Shiro 工程

引入maven依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.2</version>
    </dependency>

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.4.0</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
</dependencies>

自定义 Realm 认证授权、加密处理

doGetAuthenticationInfo方法:
用户登录认证方法,AuthenticationToken 参数就是登录接口中Subject发起login方法传的UsernamePasswordToken类。根据获取到的用户名查询用户信息并填充SimpleAuthenticationInfo返回对象。SimpleAuthenticationInfo第一个参数是用户信息、第二个参数是用户密码、第三个参数是 Realm 名称,setCredentialsSalt设置该用户的盐,因为用户密码采用了MD5加密加盐算法。

doGetAuthorizationInfo方法:
登录后授权认证方法,principalCollection参数就是doGetAuthenticationInfo方法中SimpleAuthenticationInfo返回对象中第一个传入的user用户信息。根据用户信息查询用户拥有的角色和权限信息并填充SimpleAuthorizationInfo返回对象。

HashedCredentialsMatcher类:
加密处理。创建 HashedCredentialsMatcher ,指定算法名称和 HashIterations(加密次数),将 HashedCredentialsMatcher 注入到 realm 中。

@Component
public class ShiroRealm extends AuthorizingRealm {


    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private PermissionService permissionService;

    // 加密处理
    {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(1024);
        this.setCredentialsMatcher(matcher);
    }

    /**
     * 用户认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 获取用户名
        String username = (String) authenticationToken.getPrincipal();

        // 判断用户名(非空)
        if(StringUtils.isEmpty(username)){
            // 返回null,默认抛出一个异常:org.apache.shiro.authc.UnknownAccountException
            return null;
        }

        // 根据用户名查询用户
        User user = userService.findByUsername(username);

        // 判断用户是否为null
        if(user == null) {
            return null;
        }

        // 声明 SimpleAuthenticationInfo 对象,并填充用户信息
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), "CustomRealm");

        // 设置盐
        simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));

        return simpleAuthenticationInfo;
    }

    /***
     * 授权认证
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // 判断是否认证
        Subject subject = SecurityUtils.getSubject();
        if (subject == null || !subject.isAuthenticated()) {
            return null;
        }

        // 获取认证用户
        User user = (User) principalCollection.getPrimaryPrincipal();

        // 根据用户获取当前用户拥有的角色
        Set<Role> roleSet = roleService.findRolesByUid(user.getId());
        Set<Long> roleIdSet = new HashSet<>();
        Set<String> roleNameSet = new HashSet<>();
        for (Role role : roleSet) {
            roleIdSet.add(role.getId());
            roleNameSet.add(role.getRoleName());
        }

        // 根据用户拥有的角色查询权限信息
        Set<Permission> permSet = permissionService.findPermsByRoleSet(roleIdSet);
        Set<String> permNameSet = new HashSet<>();
        for (Permission permission : permSet) {
            permNameSet.add(permission.getPermName());
        }


        // 声明 SimpleAuthorizationInfo 对象,并填充角色信息和权限信息
        SimpleAuthorizationInfo simpleAuthenticationInfo = new SimpleAuthorizationInfo();
        simpleAuthenticationInfo.setRoles(roleNameSet);
        simpleAuthenticationInfo.setStringPermissions(permNameSet);

        return simpleAuthenticationInfo;
    }
}

配置过滤器链

以下是Shiro中常用的过滤器:

  • anon(AnonymousFilter):匿名过滤器,用于表示该URL可以匿名访问,不需要进行认证和授权。
  • authc(FormAuthenticationFilter):身份认证过滤器,用于表示该URL需要进行身份认证,用户必须登录才能访问。
  • logout(LogoutFilter):登出过滤器,用于处理用户登出操作。
  • roles(RolesAuthorizationFilter):角色授权过滤器,用于判断用户是否具有指定 角色。
  • perms(PermissionsAuthorizationFilter):权限授权过滤器,用于判断用户是否具有指定权限。
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition shiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap();
        filterChainDefinitionMap.put("/login.html","anon");
        filterChainDefinitionMap.put("/user/**","anon");
        filterChainDefinitionMap.put("/test/rememberMe","user");
        filterChainDefinitionMap.put("/test/authentication","authc");
        filterChainDefinitionMap.put("/test/select","roles[超级管理员,运营管理员]");
        filterChainDefinitionMap.put("/**","authc");
        shiroFilterChainDefinition.addPathDefinitions(filterChainDefinitionMap);
        return shiroFilterChainDefinition;
    }

application.yml

shiro:
  loginUrl: /login.html
  # 针对过滤器链生效,针对注解是不生效的
  unauthorizedUrl: /401.html

如果用户未认证会默认跳到login.html页面,如果用户认证了但是权限不够会默认跳到/401.html页面。这是都是可配置的。

自定义过滤器

以下是自定义一个满足角色集合其中一个角色就放行的过滤器。

public class RolesOrAuthorizationFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        // 获取主体subject
        Subject subject = getSubject(request, response);
        // 将传入的角色转成数组操作
        String[] rolesArray = (String[]) mappedValue;
        // 健壮性校验
        if (rolesArray == null || rolesArray.length == 0) {
            return true;
        }
        // 开始校验
        for (String role : rolesArray) {
            if(subject.hasRole(role)){
                return true;
            }
        }
        return false;
    }
}

注入到 ShiroFilterFactoryBean

    @Bean
    protected ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager, ShiroFilterChainDefinition shiroFilterChainDefinition) {
        // 构建ShiroFilterFactoryBean工厂
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();

        // 设置了大量的路径
        filterFactoryBean.setLoginUrl(loginUrl);
        filterFactoryBean.setSuccessUrl(successUrl);
        filterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        // 设置安全管理器
        filterFactoryBean.setSecurityManager(securityManager);

        // 设置过滤器链
        filterFactoryBean.setFilterChainDefinitionMap(shiroFilterChainDefinition.getFilterChainMap());

        // 设置自定义过滤器 , 这里一定要手动的new出来这个自定义过滤器,如果使用Spring管理自定义过滤器,会造成无法获取到Subject
        filterFactoryBean.getFilters().put("rolesOr", new RolesOrAuthorizationFilter());

        // 返回工厂
        return filterFactoryBean;
    }

注解授权

在Shiro中,权限注解用于在代码中标记需要进行权限控制的方法或类。Shiro提供了多种权限注解,常用的包括:

  • @RequiresPermissions:用于标记需要进行权限验证的方法,指定需要的权限字符串。例如:@RequiresPermissions(“user:create”) 表示当前方法需要用户具有"user:create"权限才能访问。

  • @RequiresRoles:用于标记需要进行角色验证的方法,指定需要的角色名称。例如:@RequiresRoles(“admin”) 表示当前方法需要用户具有"admin"角色才能访问。

  • @RequiresGuest:用于标记当前方法只允许未认证的用户访问,即游客访问。

  • @RequiresUser:用于标记当前方法只允许已认证的用户访问,即已登录用户访问。

  • @RequiresAuthentication:用于标记当前方法需要进行身份验证才能访问,即需要用户进行登录认证。

这些注解可以在方法级别或类级别进行标记,以实现对应的权限控制。通过在代码中使用这些注解,可以方便地实现基于Shiro的权限控制功能。

    @GetMapping("/update")
    @RequiresRoles(value = {"超级管理员","运营管理员"})
    public String update(){
        return "item Update!!!";
    }

    @GetMapping("/insert")
    @RequiresRoles(value = {"超级管理员","运营管理员"}, logical = Logical.OR)
    public String insert(){
        return "item Update!!!";
    }

注意: 注解的形式无法将错误页面的信息定位到401.html,因为配置的这种路径,只针对过滤器链有效,注解无效。为了实现友好提示的效果,可以配置异常处理器,@RestControllerAdvice@ControllerAdvice

Shiro 解决分布式 Session

在服务搭建集群后,或者是服务是分布式架构的,导致单台服务的认证无法让其他服务也得知到信息:

  • 基于Nginx做ip_hash策略,但是也只是针对单台服务搭建集群有效果
  • 基于Shiro提供的SessionDAO解决,让SessionDAO去与公共的Redis进行交互,存储用户信息

第二章:企业级 Spring Boot 整合 Shiro 安全框架(详解优化版)(含项目代码)_第5张图片
声明SessionDAO的实现类,并重写核心方法

@Component
public class RedisSessionDAO extends AbstractSessionDAO {

    private final String SHIOR_SESSION = "session:";

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = this.generateSessionId(session);
        this.assignSessionId(session, sessionId);
        redisTemplate.opsForValue().set(SHIOR_SESSION + sessionId, session, 30, TimeUnit.MINUTES);
        return sessionId;
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        if (sessionId == null) {
            return null;
        }
        Session session = (Session) redisTemplate.opsForValue().get(SHIOR_SESSION + sessionId);
        if (session != null) {
            redisTemplate.expire(SHIOR_SESSION + sessionId,30,TimeUnit.MINUTES);
        }
        return session;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        if (session == null) {
            return;
        }
        redisTemplate.opsForValue().set(SHIOR_SESSION + session.getId(), session, 30, TimeUnit.MINUTES);
    }

    @Override
    public void delete(Session session) {
        if (session == null) {
            return;
        }
        redisTemplate.delete(SHIOR_SESSION + session.getId());
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<String> keys = redisTemplate.keys(SHIOR_SESSION + "*");
        Set<Session> sessionSet = new HashSet<>();

        // 使用Redis管道
        List<Object> results = redisTemplate.executePipelined((RedisConnection connection) -> {
            for (String key : keys) {
                connection.get(key.toString().getBytes());
            }
            return null;
        });

        // 处理管道返回的结果
        for (Object result : results) {
            if (result != null) {
                Session session = (Session) result;
                sessionSet.add(session);
            }
        }

        return sessionSet;
    }
}

RedisSessionDAO交给SessionManager

 @Bean
 public SessionManager sessionManager(RedisSessionDAO sessionDAO) {
      DefaultRedisWebSessionManager sessionManager = new DefaultRedisWebSessionManager();
      sessionManager.setSessionDAO(sessionDAO);
      return sessionManager;
  }

SessionManager注入到SecurityManager

@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm realm, SessionManager sessionManager, RedisCacheManager redisCacheManager){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);
    securityManager.setSessionManager(sessionManager);
    securityManager.setCacheManager(redisCacheManager);
    return securityManager;
}

性能优化

RedisSessionDAO 问题

将传统的ConcurrentHashMap切换为Redis之后,发现每次请求需要访问多次Redis服务,这个访问的频次会出现很长时间的IO等待,对每次请求的性能减低了,并且对Redis的压力也提高了。

解决方案:基于装饰者模式重新声明SessionManager中提供的retrieveSession方法,让每次请求先去request域中查询session信息,request域中没有,再去Redis中查询。

public class DefaultRedisWebSessionManager extends DefaultWebSessionManager {

    @Override
    protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
        Serializable sessionId = getSessionId(sessionKey);

        if(sessionKey instanceof WebSessionKey){
            WebSessionKey webSessionKey = (WebSessionKey) sessionKey;
            ServletRequest request = webSessionKey.getServletRequest();
            Session session = (Session) request.getAttribute(sessionId + "");
            if(session != null){
                return session;
            }else{
                session = retrieveSessionFromDataSource(sessionId);
                if (session == null) {
                    String msg = "Could not find session with ID [" + sessionId + "]";
                    throw new UnknownSessionException(msg);
                }
                request.setAttribute(sessionId + "", session);
                return session;
            }
        }
        return null;
    }
}

配置DefaultRedisWebSessionManagerSecurityManager

@Bean
public SessionManager sessionManager(RedisSessionDAO sessionDAO) {
     DefaultRedisWebSessionManager sessionManager = new DefaultRedisWebSessionManager();
     sessionManager.setSessionDAO(sessionDAO);
     return sessionManager;
 }

Shiro 授权进行缓存

如果后台接口存在授权操作,那么每次请求都需要去数据库查询对应的角色信息和权限信息,对数据库来说,这样的查询压力太大了。

在Shiro中,发现每次在执行自定义Realm的授权方法查询数据库之前,会有一个执行Cache的操作。

先从Cache中基于一个固定的key去查询角色以及权限的信息。

只需要提供好响应的CacheManager实例,还要实现一个与Redis交互的Cache对象,将Cache对象设置到CacheManager实例中。

实现 RedisCache

@Component
public class RedisCache<K, V> implements Cache<K, V> {

    @Resource
    private RedisTemplate redisTemplate;

    private final String CACHE_PREFIX = "cache:";

    @Override
    public V get(K k) throws CacheException {
        V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
        if(v != null){
            redisTemplate.expire(CACHE_PREFIX + k,15, TimeUnit.MINUTES);
        }
        return v;
    }

    @Override
    public V put(K k, V v) throws CacheException {
        redisTemplate.opsForValue().set(CACHE_PREFIX + k, v,15, TimeUnit.MINUTES);
        return v;
    }

    @Override
    public V remove(K k) throws CacheException {
        V v = (V) redisTemplate.opsForValue().get(CACHE_PREFIX + k);
        if(v != null){
            redisTemplate.delete(CACHE_PREFIX + k);
        }
        return v;
    }

    public void clear() throws CacheException {
        Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
        redisTemplate.delete(keys);
    }

    @Override
    public int size() {
        Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
        return keys.size();
    }

    @Override
    public Set<K> keys() {
        Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
        return keys;
    }

    @Override
    public Collection<V> values() {
        Set values = new HashSet();
        Set keys = redisTemplate.keys(CACHE_PREFIX + "*");
        for (Object key : keys) {
            Object value = redisTemplate.opsForValue().get(key);
            values.add(value);
        }
        return values;
    }
}

实现 CachaManager

@Component
public class RedisCacheManager implements CacheManager {

    @Resource
    private RedisCache redisCache;

    @Override
    public <K, V> Cache<K, V> getCache(String s) throws CacheException {
        return redisCache;
    }
}

RedisCacheManager配置到SecurityManager

@Bean
public DefaultWebSecurityManager securityManager(ShiroRealm realm, SessionManager sessionManager, RedisCacheManager redisCacheManager){
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(realm);
    securityManager.setSessionManager(sessionManager);
    securityManager.setCacheManager(redisCacheManager);
    return securityManager;
}

查询记录

查询Redis
查询Mysql数据库
查询Redis
查询Redis
查询Redis
查询Redis
查询Redis
查询Redis
查询Redis

你可能感兴趣的:(Shiro,安全框架,spring,boot,安全)