在 上一篇文章 主要讲了Shiro
权限授权和认证跳过。本篇文章就主要讲解如何整合Shiro
和Redis
。这样就避免携带同一个Token
的时候,每次取查询数据库了。可以利用一个缓存,减轻DB
的压力。
添加pom
依赖:
org.springframework.boot spring-boot-starter-data-redis
我们可以在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 # 连接池的超时时间(毫秒)
添加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*/@Beanpublic RedisTemplate functionDomainRedisTemplate(RedisConnectionFactory redisConnectionFactory) {RedisTemplate redisTemplate = new RedisTemplate<>();initDomainRedisTemplate(redisTemplate, redisConnectionFactory);return redisTemplate;}/*** 设置数据存入 redis 的序列化方式,并开启事务** @param redisTemplate* @param factory*/private void initDomainRedisTemplate(RedisTemplate 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.我们自定义一个缓存管理器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 {@Overridepublic Cache getCache(String s) throws CacheException {return new RedisCache(s);}
}
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 implements Cache {private String cacheName;public RedisCache(String cacheName) {this.cacheName = cacheName;}private RedisTemplate getRedisTemplate() {RedisTemplate redisTemplate = SpringBeanUtil.getBean(RedisTemplate.class);return redisTemplate;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic V remove(K k) throws CacheException {return (V) getRedisTemplate().opsForHash().delete(cacheName, k.toString());}@Overridepublic void clear() throws CacheException {getRedisTemplate().delete(cacheName);}@Overridepublic int size() {return getRedisTemplate().opsForHash().size(cacheName).intValue();}@Overridepublic Set keys() {return getRedisTemplate().opsForHash().keys(cacheName);}@Overridepublic Collection values() {return getRedisTemplate().opsForHash().values(cacheName);}
}
3.常量JwtConstant
:
public class JwtConstant {public static final String USER_ID = "userId";public static final Integer TIMEOUT = 10;
}
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.我们先登录一下:拿到一个最新的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 "用户信息为空";
}
相当于:
doGetAuthenticationInfo
:第一次认证的时候,插入了一条缓存。doGetAuthorizationInfo
:第二次身份校验的时候,又更新了一次缓存。3.第二次访问:可见没有插入的日志了。同时Degbug
打个断点也能发现,上面两个函数,只有第一次执行的时候会访问。后续就不会再进入了。因为缓存的原因。
我们存入Redis
中的hashKey
就是我们的Token
。所以在有效时间内,带着相同Token
的请求,都不需要经过认证了。
上一篇:MySQL约束和表的复杂查询操作
下一篇:ArcGIS计算地形湿度指数