• Java - SpringBoot整合Shiro之缓存功能


    前言

    上一篇文章 主要讲了Shiro权限授权和认证跳过。本篇文章就主要讲解如何整合ShiroRedis。这样就避免携带同一个Token的时候,每次取查询数据库了。可以利用一个缓存,减轻DB的压力。

    一. SpringBoot 整合Redis

    添加pom依赖:

    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    1.1 配置 RedisTemplate

    我们可以在application.yml文件中添加Redis相关的配置:

    spring:
      redis:
        database: 0 # Redis数据库索引(默认为0)
        host: xxx # Redis的服务地址
        port: xxx # Redis的服务端口
        password: xxx # Redis密码
        jedis:
          pool:
            max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
            max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
            max-idle: 8 # 连接池中的最大空闲连接
            min-idle: 0 # 连接池中的最小空闲链接
        timeout: 30000 # 连接池的超时时间(毫秒)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    添加Redis的配置类RedisConfig

    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.RedisTemplate;
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * @Date 2022/11/15 13:56
     * @Created by jj.lin
     */
    @Configuration
    public class RedisConfig {
        /**
         * 实例化 RedisTemplate 对象
         *
         * @return
         */
        @Bean
        public RedisTemplate<String, Object> functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
            initDomainRedisTemplate(redisTemplate, redisConnectionFactory);
            return redisTemplate;
        }
        /**
         * 设置数据存入 redis 的序列化方式,并开启事务
         *
         * @param redisTemplate
         * @param factory
         */
        private void initDomainRedisTemplate(RedisTemplate<String, Object> redisTemplate, RedisConnectionFactory factory) {
            // 配置序列化器。如果不配置Serializer,那么存储的时候缺省使用String,如果用User类型存储,那么会提示错误User can't cast to String!
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
    
            // 开启事务
            redisTemplate.setEnableTransactionSupport(true);
            redisTemplate.setConnectionFactory(factory);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    1.2 Shiro整合Redis缓存配置

    1.我们自定义一个缓存管理器RedisCacheManager,需要继承CacheManager

    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.apache.shiro.cache.CacheManager;
    
    /**
     * @Date 2022/11/15 13:52
     * @Created by jj.lin
     */
    public class RedisCacheManager implements CacheManager {
        @Override
        public <K, V> Cache<K, V> getCache(String s) throws CacheException {
            return new RedisCache<K,V>(s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2.上面的RedisCache是我们自定义的实现,我们需要重写相关的put/get函数。这样Shiro在认证的时候,就会通过我们自定义的put/get函数去存储/获得缓存了。

    import com.pro.config.SpringBeanUtil;
    import com.pro.constant.JwtConstant;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.cache.Cache;
    import org.apache.shiro.cache.CacheException;
    import org.springframework.data.redis.core.RedisTemplate;
    
    import java.util.Collection;
    import java.util.Set;
    import java.util.concurrent.TimeUnit;
    
    /**
     * @Date 2022/11/15 13:52
     * @Created by jj.lin
     */
    @Slf4j
    public class RedisCache<K, V> implements Cache<K, V> {
        private String cacheName;
    
        public RedisCache(String cacheName) {
            this.cacheName = cacheName;
        }
        
        private RedisTemplate getRedisTemplate() {
            RedisTemplate redisTemplate = SpringBeanUtil.getBean(RedisTemplate.class);
            return redisTemplate;
        }
    
        @Override
        public V get(K k) throws CacheException {
            RedisTemplate redisTemplate = getRedisTemplate();
            V v = (V) redisTemplate.opsForHash().get(cacheName, k.toString());
            Long expire = redisTemplate.getExpire(cacheName);
            log.info("从 {} 中获取Key:{}, Value:{},剩余缓存时长:{}", cacheName, k, v, expire);
            return v;
        }
    
        @Override
        public V put(K k, V v) throws CacheException {
            log.info("从 {} 中插入Key:{}, Value:{}", cacheName, k, v);
            RedisTemplate redisTemplate = getRedisTemplate();
            redisTemplate.opsForHash().put(cacheName, k.toString(), v);
            // 可以设置一个缓存的超时时间
            redisTemplate.expire(cacheName, JwtConstant.TIMEOUT, TimeUnit.MINUTES);
            return null;
        }
    
        @Override
        public V remove(K k) throws CacheException {
            return (V) getRedisTemplate().opsForHash().delete(cacheName, k.toString());
        }
    
        @Override
        public void clear() throws CacheException {
            getRedisTemplate().delete(cacheName);
        }
    
        @Override
        public int size() {
            return getRedisTemplate().opsForHash().size(cacheName).intValue();
        }
    
        @Override
        public Set<K> keys() {
            return getRedisTemplate().opsForHash().keys(cacheName);
        }
    
        @Override
        public Collection<V> values() {
            return getRedisTemplate().opsForHash().values(cacheName);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    3.常量JwtConstant

    public class JwtConstant {
        public static final String USER_ID = "userId";
        public static final Integer TIMEOUT = 10;
    }
    
    • 1
    • 2
    • 3
    • 4

    4.然后Shiro开启缓存功能的配置ShiroConfig

    原代码:
    @Bean
    public Realm realm() {
        JwtRealm jwtRealm = new JwtRealm();
        return jwtRealm;
    }
    新代码:
    @Bean
    public Realm realm() {
        JwtRealm jwtRealm = new JwtRealm();
    
        // 开启缓存,设置缓存管理器
        jwtRealm.setCachingEnabled(true);
        jwtRealm.setAuthenticationCachingEnabled(true);
        jwtRealm.setAuthorizationCachingEnabled(true);
        jwtRealm.setCacheManager(new RedisCacheManager());
        return jwtRealm;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1.3 测试

    1.我们先登录一下:拿到一个最新的Token
    在这里插入图片描述

    2.第一次访问getUser接口(需要经过JWT认证):
    在这里插入图片描述
    为什么这里有两次插入?因为我getUser接口还有身份校验的过程:

    @RequiresRoles("user")
    @PostMapping("/getUser")
    public String getUser() {
        Long userId = JwtUtil.getUserId();
        Subject currentUser = SecurityUtils.getSubject();
        if (userId != null) {
            return "成功拿到用户信息: " + currentUser.getPrincipal();
        }
        return "用户信息为空";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    相当于:

    • doGetAuthenticationInfo:第一次认证的时候,插入了一条缓存。
    • doGetAuthorizationInfo:第二次身份校验的时候,又更新了一次缓存。

    3.第二次访问:可见没有插入的日志了。同时Degbug打个断点也能发现,上面两个函数,只有第一次执行的时候会访问。后续就不会再进入了。因为缓存的原因。
    在这里插入图片描述

    我们存入Redis中的hashKey就是我们的Token。所以在有效时间内,带着相同Token的请求,都不需要经过认证了。

  • 相关阅读:
    《安富莱嵌入式周报》第276期:2022.07.25--2022.07.3
    123. 买卖股票的最佳时机 III
    内网离线安装elasticsearch、kibana
    【矩阵论】4.矩阵运算——广义逆——定义性质与特殊矩阵的广义逆
    如果面试时,问你职业规划怎么答?
    【面试八股总结】MySQL索引(二):B+树数据结构、索引使用场景、索引优化、索引失效
    1700*C. Number of Ways(贪心&前缀和)
    渗透测试CTF-图片隐写的详细教程2(干货)
    数据库连接性比较:Navicat 和基于 Java 的工具
    煤矿安全大模型:微调internlm2模型实现针对煤矿事故和煤矿安全知识的智能问答
  • 原文地址:https://blog.csdn.net/Zong_0915/article/details/127864452