第六章 Realm缓存机制
1、Realm缓存机制意义
在上面我们自定了自己的realm,但是我们发现
在认证和授权的时候,程序需要频繁的访问数据库,这样对于数据库的压力可想而知,那我们怎么处理呢?
2、Realm缓存机制实现思路
【1】缓存机制图解
【2】原理分析
此时我们对UserBridgeServiceImpl的实现类里面的逻辑加入了自定义的SimpleCacheService缓存服务接口,简单来说实现了在认证和鉴权时不需要每次都去查询数据库,而是把认证和鉴权信息放入到redis缓存中,以减低数据库的访问压力
1、集成redis服务器,作为集中存储认证和鉴权信息 2、改写UserBridgeServiceImpl使其优先从缓存中读取
3、redission集成
【1】添加ShiroRedisProperties
此类主要负责yaml文件的配置类
package com.itheima.shiro.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
/**
* @Description redis配置文件
*/
@Data
@ConfigurationProperties(prefix = "itheima.framework.shiro.redis")
public class ShiroRedisProperties implements Serializable {
/**
* redis连接地址
*/
private String nodes ;
/**
* 获取连接超时时间
*/
private int connectTimeout ;
/**
* 连接池大小
*/
private int connectPoolSize;
/**
* 初始化连接数
*/
private int connectionMinimumidleSize ;
/**
* 等待数据返回超时时间
*/
private int timeout ;
/**
* 全局超时时间
*/
private long globalSessionTimeout;
}
【2】编辑ShiroConfig
集成redisson的相关配置,同时启用ShiroRedisProperties的配置
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.ShiroDbRealmImpl;
import com.itheima.shiro.filter.RolesOrAuthorizationFilter;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description 权限配置类
*/
@Configuration
@ComponentScan(basePackages = "com.itheima.shiro.core")
@EnableConfigurationProperties({ShiroRedisProperties.class})
@Log4j2
public class ShiroConfig {
@Autowired
private ShiroRedisProperties shiroRedisProperties;
/**
* @Description redission客户端
*/
@Bean("redissonClientForShiro")
public RedissonClient redissonClient() {
log.info("=====初始化redissonClientForShiro开始======");
String[] nodeList = shiroRedisProperties.getNodes().split(",");
Config config = new Config();
if (nodeList.length == 1) {
config.useSingleServer().setAddress(nodeList[0])
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
} else {
config.useClusterServers().addNodeAddress(nodeList)
.setConnectTimeout(shiroRedisProperties.getConnectTimeout())
.setMasterConnectionMinimumIdleSize(shiroRedisProperties.getConnectionMinimumidleSize())
.setMasterConnectionPoolSize(shiroRedisProperties.getConnectPoolSize()).setTimeout(shiroRedisProperties.getTimeout());
}
RedissonClient redissonClient = Redisson.create(config);
log.info("=====初始化redissonClientForShiro完成======");
return redissonClient;
}
/**
* @Description 创建cookie对象
*/
@Bean(name="sessionIdCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
/**
* @Description 权限管理器
* @param
* @return
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroDbRealm());
securityManager.setSessionManager(shiroSessionManager());
return securityManager;
}
/**
* @Description 自定义RealmImpl
*/
@Bean(name="shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
/**
* @Description 会话管理器
*/
@Bean(name="sessionManager")
public DefaultWebSessionManager shiroSessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionValidationSchedulerEnabled(false);
sessionManager.setSessionIdCookieEnabled(true);
sessionManager.setSessionIdCookie(simpleCookie());
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
/**
* @Description 保证实现了Shiro内部lifecycle函数的bean执行
*/
@Bean(name = "lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return new AuthorizationAttributeSourceAdvisor();
}
/**
* @Description 过滤器链
*/
private Map<String, String> filterChainDefinition(){
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
Map<String, String> map = new LinkedHashMap<>();
for (Object object : list) {
String key = object.toString();
String value = PropertiesUtil.getShiroValue(key);
log.info("读取防止盗链控制:---key{},---value:{}",key,value);
map.put(key, value);
}
return map;
}
/**
* @Description 自定义过滤器定义
*/
private Map<String, Filter> filters() {
Map<String, Filter> map = new HashMap<String, Filter>();
map.put("roleOr", new RolesOrAuthorizationFilter());
return map;
}
/**
* @Description Shiro过滤器
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(defaultWebSecurityManager());
//使自定义过滤器生效
shiroFilter.setFilters(filters());
shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
shiroFilter.setLoginUrl("/login");
shiroFilter.setUnauthorizedUrl("/login");
return shiroFilter;
}
}
4、缓存对象SimpleMapCache
package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
* @Description 缓存实现类, 实现序列 接口方便对象存储于第三方容器(Map存放键值对)
*/
public class SimpleMapCache implements Cache<Object, Object>, Serializable {
private final Map<Object, Object> attributes;
private final String name;
public SimpleMapCache(String name, Map<Object, Object> backingMap) {
if (name == null)
throw new IllegalArgumentException("Cache name cannot be null.");
if (backingMap == null) {
throw new IllegalArgumentException("Backing map cannot be null.");
} else {
this.name = name;
attributes = backingMap;
}
}
public Object get(Object key) throws CacheException {
return attributes.get(key);
}
public Object put(Object key, Object value) throws CacheException {
return attributes.put(key, value);
}
public Object remove(Object key) throws CacheException {
return attributes.remove(key);
}
public void clear() throws CacheException {
attributes.clear();
}
public int size() {
return attributes.size();
}
public Set<Object> keys() {
Set<Object> keys = attributes.keySet();
if (!keys.isEmpty())
return Collections.unmodifiableSet(keys);
else
return Collections.emptySet();
}
public Collection<Object> values() {
Collection<Object> values = attributes.values();
if (!EmptyUtil.isNullOrEmpty(values))
return Collections.unmodifiableCollection(values);
else
return Collections.emptySet();
}
@Override
public String toString() {
return "SimpleMapCache [attributes=" + attributes + ", name=" + name
+ ", keys()=" + keys() + ", size()=" + size() + ", values()="
+ values() + "]";
}
}
5、ShiroRedissionSerialize序列化工具
package com.itheima.shiro.utils;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.codec.Base64;
import java.io.*;
/**
* @Description:实现shiro会话的序列化存储
*/
@Log4j2
public class ShiroRedissionSerialize {
public static Object deserialize(String str) {
if (EmptyUtil.isNullOrEmpty(str)) {
return null;
}
ByteArrayInputStream bis = null;
ObjectInputStream ois = null;
Object object=null;
try {
bis = new ByteArrayInputStream(EncodesUtil.decodeBase64(str));
ois = new ObjectInputStream(bis);
object = ois.readObject();
} catch (IOException |ClassNotFoundException e) {
log.error("流读取异常:{}",e);
} finally {
try {
bis.close();
ois.close();
} catch (IOException e) {
log.error("流读取异常:{}",e);
}
}
return object;
}
public static String serialize(Object obj) {
if (EmptyUtil.isNullOrEmpty(obj)) {
return null;
}
ByteArrayOutputStream bos = null;
ObjectOutputStream oos = null;
String base64String = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(obj);
base64String = EncodesUtil.encodeBase64(bos.toByteArray());
} catch (IOException e) {
log.error("流写入异常:{}",e);
} finally {
try {
bos.close();
oos.close();
} catch (IOException e) {
log.error("流写入异常:{}",e);
}
}
return base64String;
}
}
6、缓存服务接口SimpleCacheService
SimpleCacheService
package com.itheima.shiro.core;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
/**
* @Description 简单的缓存管理接口
*/
public interface SimpleCacheService {
/**
* <b>功能说明:</b>:新增缓存堆到管理器<br>
*/
void createCache(String cacheName, Cache<Object, Object> cache) throws CacheException;
/**
* <b>方法名:</b>:getCache<br>
* <b>功能说明:</b>:获取缓存堆<br>
*/
Cache<Object, Object> getCache(String cacheName) throws CacheException;
/**
* <b>方法名:</b>:removeCache<br>
* <b>功能说明:</b>:移除缓存堆<br>
*/
void removeCache(String cacheName) throws CacheException;
/**
* <b>方法名:</b>:updateCahce<br>
* <b>功能说明:</b>:更新缓存堆<br>
*/
void updateCahce(String cacheName, Cache<Object, Object> cache) throws CacheException;
}
SimpleCacheServiceImpl
调用RedissonClient去实现缓存,同时使用ShiroRedissionSerialize实现序列化
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.CacheConstant;
import com.itheima.shiro.core.SimpleCacheService;
import com.itheima.shiro.utils.ShiroRedissionSerialize;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheException;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
*
* @Description 简单的缓存管理接口的实现
*/
@Log4j2
@Component
public class SimpleCacheServiceImpl implements SimpleCacheService {
@Resource(name = "redissonClientForShiro")
RedissonClient redissonClient;
@Override
public void createCache(String name, Cache<Object, Object> cache){
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.trySet(ShiroRedissionSerialize.serialize(cache), SecurityUtils.getSubject().getSession().getTimeout()/1000, TimeUnit.SECONDS);
}
@SuppressWarnings("unchecked")
@Override
public Cache<Object, Object> getCache(String name) throws CacheException {
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
return (Cache<Object, Object>) ShiroRedissionSerialize.deserialize(bucket.get());
}
@Override
public void removeCache(String name) throws CacheException {
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.delete();
}
@Override
public void updateCahce(String name, Cache<Object, Object> cache){
RBucket<String> bucket = redissonClient.getBucket(CacheConstant.GROUP_CAS+name);
bucket.set(ShiroRedissionSerialize.serialize(cache), SecurityUtils.getSubject().getSession().getTimeout()/1000, TimeUnit.MILLISECONDS);
}
}