应用场景
用户权限认证需要查询数据库中角色与权限关系,Shiro负责权限认证。用户请求资源,后端逻辑都需要查询数据库中的信息交给Shiro做权限验证,这种频发查询准静态数据的做法会消耗数据库资源同时也会API性能。基于Redis缓存用户角色关系,可以有效提供API性能。
环境说明
JDK 1.8
Springboot 2.1.6
Redis 5.0
Shiro依赖包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Redis配置
Redis
的value
序列化方式需要指定Jackson2JsonRedisSerializer
,需要对org.apache.shiro.authz.SimpleAuthorizationInfo
序列化。
@Bean
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
// 设置序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(
Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
RedisSerializer<?> stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);// key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);// value序列化
redisTemplate.setHashKeySerializer(stringSerializer);// Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);// Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
继承CacheManger
@Slf4j
public class RedisShiroCacheManager extends AbstractCacheManager {
private RedisService redisService;
public RedisShiroCacheManager(RedisService redisService) {
this.redisService = redisService;
}
/**
* 认证或者授权对应的缓存名称key
*/
@Override
protected Cache createCache(String s) throws CacheException {
// 将Spring Bean redisService注入到RedisShiroCache
Cache cache = new RedisShiroCache( redisService, s);
return cache;
}
}
实现Redis读写
@Slf4j
public class RedisShiroCache<K, V> implements Cache<K, V> {
private RedisService redisService;
private String cacheName;
public RedisShiroCache() {
}
public RedisShiroCache(RedisService redisService, String cacheName) {
this.redisService = redisService;
this.cacheName = cacheName;
}
@Override
public V get(K k) throws CacheException {
Object hget = redisService.hget(this.cacheName, k.toString());
log.info("get({}) and value [{}]", k, hget);
return (V) hget;
}
@Override
public V put(K k, V v) throws CacheException {
redisService.hset(this.cacheName, k.toString(), v);
log.info("set({},{}", k, v);
return null;
}
@Override
public V remove(K k) throws CacheException {
return null;
}
@Override
public void clear() throws CacheException {
}
@Override
public int size() {
return 0;
}
@Override
public Set<K> keys() {
return null;
}
@Override
public Collection<V> values() {
return null;
}
}
配置securityManager
@Slf4j
@Configuration
public class ShirosConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
log.info("注入Shiro的Web过滤器-->shiroFilter", ShiroFilterFactoryBean.class);
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
......
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, RedisService redisService) {
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager();
/**
* 设置Redis缓存
*/
// 开启缓存
oAuth2Realm.setCachingEnabled(true);
oAuth2Realm.setAuthorizationCachingEnabled(true);
oAuth2Realm.setAuthorizationCacheName("authorizationCache");
oAuth2Realm.setAuthorizationCachingEnabled(true);
oAuth2Realm.setAuthenticationCacheName("authenticationCache");
// 指定缓存管理器
defaultSecurityManager.setCacheManager(new RedisShiroCacheManager(redisService));
defaultSecurityManager.setRealm(oAuth2Realm);
return defaultSecurityManager;
}
计划
- 主要对授权部分,缓存角色权限信息;但是每次调用
API
还是会调用doGetAuthenticationInfo
方法,这就涉及到session
缓存; - 既然使用到了缓存,就需要保证缓存数据与
Mysql
一致性。 - 开发RememberMe功能
- 同一账号不能同时登录
- 熟悉Shiro工作原理,不能硬着头皮写代码,否则效率十分低。
引用
- https://blog.csdn.net/u010514380/article/details/82185451 【很详细】
- https://blog.csdn.net/baidu_33969289/article/details/86670054
- https://www.cnblogs.com/youzhibing/tag/shiro/