栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

redisson集成spring-session和shiro实现分布式session

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

redisson集成spring-session和shiro实现分布式session

一、pom
       
        
            org.apache.shiro
            shiro-spring-boot-web-starter
            1.6.0
        
        
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        
         
        
        
            org.redisson
            redisson-spring-boot-starter
            3.16.3
        
        
        
            org.springframework.session
            spring-session-data-redis
        
二、使用redisson的方式实现shiro的cache和cachemanager
  • RedissonShiroCacheManager
@Component
public class RedissonShiroCacheManager implements CacheManager, Initializable {
    private boolean allowNullValues = true;
    private Codec codec = new JsonJacksonCodec();
    private RedissonClient redisson;
    private String configLocation;
    private Map configMap = new ConcurrentHashMap<>();
    private ConcurrentMap instanceMap = new ConcurrentHashMap<>();
    public RedissonShiroCacheManager(){}
    public RedissonShiroCacheManager(RedissonClient redisson){
        this(redisson, (String)null, null);
    }
    public RedissonShiroCacheManager(RedissonClient redisson, Map config) {
        this(redisson, config, null);
    }
    public RedissonShiroCacheManager(RedissonClient redisson, Map config, Codec codec) {
        this.redisson = redisson;
        this.configMap = (Map) config;
        if (codec != null) {
            this.codec = codec;
        }
    }
    public RedissonShiroCacheManager(RedissonClient redisson, String configLocation) {
        this(redisson, configLocation, null);
    }
    public RedissonShiroCacheManager(RedissonClient redisson, String configLocation, Codec codec) {
        this.redisson = redisson;
        this.configLocation = configLocation;
        if (codec != null) {
            this.codec = codec;
        }
    }
    protected CacheConfig createDefaultConfig() {
        return new CacheConfig();
    }

    @Override
    public  Cache getCache(String name) throws CacheException {
        Cache cache = this.instanceMap.get(name);
        if (cache != null) {
            return cache;
        }
        CacheConfig config = this.configMap.get(name);
        if (config == null) {
            config = createDefaultConfig();
            configMap.put(name, config);
        }
        if (config.getMaxIdleTime() == 0 && config.getTTL() == 0 && config.getMaxSize() == 0) {
            return createMap(name, config);
        }
        return createMapCache(name, config);
    }
    private  Cache createMap(String name, CacheConfig config) {
        RMap map = getMap(name, config);
        Cache cache = new RedissonShiroCache<>(map, this.allowNullValues);
        Cache oldCache = this.instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        }
        return cache;
    }
    protected  RMap getMap(String name, CacheConfig config) {
        if (this.codec != null) {
            return  this.redisson.getMap(name, this.codec);
        }
        return this.redisson.getMap(name);
    }
    private  Cache createMapCache(String name, CacheConfig config) {
        RMapCache map = getMapCache(name, config);
        Cache cache = new RedissonShiroCache<>(map, config, this.allowNullValues);
        Cache oldCache = this.instanceMap.putIfAbsent(name, cache);
        if (oldCache != null) {
            cache = oldCache;
        } else {
            map.setMaxSize(config.getMaxSize());
        }
        return cache;
    }
    protected  RMapCache getMapCache(String name, CacheConfig config) {
        if (this.codec != null) {
            return this.redisson.getMapCache(name, this.codec);
        }
        return redisson.getMapCache(name);
    }
    @Override
    public void init() {
        if (this.configLocation == null) {
            return;
        }
        try {
            this.configMap = (Map) CacheConfig.fromJSON(ResourceUtils.getInputStreamForPath(this.configLocation));
        } catch (IOException e) {
            // try to read yaml
            try {
                this.configMap = (Map) CacheConfig.fromYAML(ResourceUtils.getInputStreamForPath(this.configLocation));
            } catch (IOException e1) {
                throw new IllegalArgumentException(
                        "Could not parse cache configuration at [" + configLocation + "]", e1);
            }
        }
    }
    public void setConfig(Map config) {
        this.configMap = (Map) config;
    }

    public RedissonClient getRedisson() {
        return redisson;
    }

    public void setRedisson(RedissonClient redisson) {
        this.redisson = redisson;
    }

    public Codec getCodec() {
        return codec;
    }

    public void setCodec(Codec codec) {
        this.codec = codec;
    }

    public String getConfigLocation() {
        return configLocation;
    }

    public void setConfigLocation(String configLocation) {
        this.configLocation = configLocation;
    }

    public boolean isAllowNullValues() {
        return allowNullValues;
    }

    public void setAllowNullValues(boolean allowNullValues) {
        this.allowNullValues = allowNullValues;
    }
}
  • RedissonShiroCache
public class RedissonShiroCache implements Cache {

    private RMapCache mapCache;

    private final RMap map;

    private CacheConfig config;

    private final boolean allowNullValues;

    private final AtomicLong hits = new AtomicLong();

    private final AtomicLong misses = new AtomicLong();

    public RedissonShiroCache(RMapCache mapCache, CacheConfig config, boolean allowNullValues) {
        this.mapCache = mapCache;
        this.map = mapCache;
        this.config = config;
        this.allowNullValues = allowNullValues;
    }

    public RedissonShiroCache(RMap map, boolean allowNullValues) {
        this.map = map;
        this.allowNullValues = allowNullValues;
    }

    @Override
    public V get(K key) throws CacheException {
        Object value = this.map.get(key);
        if (value == null) {
            addCacheMiss();
        } else {
            addCacheHit();
        }
        return fromStorevalue(value);
    }

    @Override
    public V put(K key, V value) throws CacheException {
        Object previous;
        if (!allowNullValues && value == null) {
            if (mapCache != null) {
                previous = mapCache.remove(key);
            } else {
                previous = map.remove(key);
            }
            return fromStorevalue(previous);
        }

        Object val = toStorevalue(value);
        if (mapCache != null) {
            previous = mapCache.put(key, val, config.getTTL(), TimeUnit.MILLISECONDS,
                    config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
        } else {
            previous = map.put(key, val);
        }
        return fromStorevalue(previous);
    }

    public void fastPut(K key, V value) throws CacheException {
        if (!allowNullValues && value == null) {
            if (mapCache != null) {
                mapCache.fastRemove(key);
            } else {
                map.fastRemove(key);
            }
            return;
        }

        Object val = toStorevalue(value);
        if (mapCache != null) {
            mapCache.fastPut(key, val, config.getTTL(), TimeUnit.MILLISECONDS,
                    config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
        } else {
            map.fastPut(key, val);
        }
    }

    public V putIfAbsent(K key, V value) throws CacheException {
        Object previous;
        if (!allowNullValues && value == null) {
            if (mapCache != null) {
                previous = mapCache.get(key);
            } else {
                previous = map.get(key);
            }
            return fromStorevalue(previous);
        }

        Object val = toStorevalue(value);
        if (mapCache != null) {
            previous = mapCache.putIfAbsent(key, val, config.getTTL(), TimeUnit.MILLISECONDS,
                    config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
        } else {
            previous = map.putIfAbsent(key, val);
        }
        return fromStorevalue(previous);
    }

    public boolean fastPutIfAbsent(K key, V value) throws CacheException {
        if (!allowNullValues && value == null) {
            return false;
        }

        Object val = toStorevalue(value);
        if (mapCache != null) {
            return mapCache.fastPutIfAbsent(key, val, config.getTTL(), TimeUnit.MILLISECONDS,
                    config.getMaxIdleTime(), TimeUnit.MILLISECONDS);
        } else {
            return map.fastPutIfAbsent(key, val);
        }
    }

    @Override
    public V remove(K key) throws CacheException {
        Object previous = this.map.remove(key);
        return fromStorevalue(previous);
    }

    public long fastRemove(K ... keys) {
        return this.map.fastRemove(keys);
    }

    @Override
    public void clear() throws CacheException {
        this.map.clear();
    }

    @Override
    public int size() {
        return this.map.size();
    }

    @Override
    public Set keys() {
        return this.map.readAllKeySet();
    }

    @Override
    public Collection values() {
        Collection innerValues = this.map.readAllValues();
        Collection res = new ArrayList<>(innerValues.size());
        for (Object val : innerValues) {
            res.add(fromStorevalue(val));
        }
        return res;
    }

    protected V fromStorevalue(Object storevalue) {
        if (storevalue instanceof NullValue) {
            return null;
        }
        return (V) storevalue;
    }
    protected Object toStorevalue(V userValue) {
        if (userValue == null) {
            return NullValue.INSTANCE;
        }
        return userValue;
    }
    
    long getCacheHits(){
        return this.hits.get();
    }
    
    long getCacheMisses(){
        return this.misses.get();
    }
    private void addCacheHit(){
        this.hits.incrementAndGet();
    }
    private void addCacheMiss(){
        this.misses.incrementAndGet();
    }
}
 
三、开启springsession,接管shiro-session 
@SpringBootApplication
//使用EnableRedisHttpSession注解开启spring分布式session,该类的作用是配置org.springframework.session.web.http.SessionRepositoryFilter进行请求拦截
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
@EnableCaching
public class GeApplication {
    public static void main(String[] args) {
        SpringApplication.run(GeApplication.class, args);
    }
}
  • shiroconfig类的配置
public class ShiroConfig {
    @Autowired
    RedissonClient redissonClient;
    
    @Bean
    public SessionManager sessionManager(){
        return new ServletContainerSessionManager();
    }
    
    @Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 设置登录url
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 设置主页url
        shiroFilterFactoryBean.setSuccessUrl("/shiro");
        // 设置未授权的url
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        Map filterChainDefinitionMap = new linkedHashMap<>();

        // 注销登录
        linkedHashMap filtersMap = new linkedHashMap<>();
        filtersMap.put("logout", shiroLogoutFilter());
        shiroFilterFactoryBean.setFilters(filtersMap);

        filterChainDefinitionMap.put("/loginOut", "logout");
        // 开放登录接口
        filterChainDefinitionMap.put("/doLogin", "anon");
        ........
        // 其余url全部拦截,必须放在最后
        filterChainDefinitionMap.put("
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreatorShiro() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    
    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
}
四、自定义shiro的登出行为,做一些springsession中与用户有关的缓存操作
  • 自定义shiro只需要实现LogoutFilter,重写prehandle方法即可
public class ShiroLogoutFilter extends LogoutFilter {
    private final RedissonClient redissonClient;
    
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        Subject subject = getSubject(request,response);
        try {
            HttpServletRequest httpReq = (HttpServletRequest) request;
            HttpSession session = httpReq.getSession();
            //根据spring session的信息,删除用户的缓存
            String sessionKey="spring:session:sessions:" + session.getId();
            String expires="spring:session:sessions:expires:" + session.getId();
            redissonClient.getBucket(sessionKey).delete();
            redissonClient.getBucket(expires).delete();
        } catch (Throwable t) {
            t.printStackTrace();
        }
        //登出
        subject.logout();
        //获取登出后重定向到的地址
        String redirectUrl = getRedirectUrl(request,response,subject);
        //重定向
        issueRedirect(request,response,redirectUrl);
        //配置登出后重定向的地址,等出后配置跳转到登录接口
        return false;
    }
    public ShiroLogoutFilter(RedissonClient redissonClient){
        this.redissonClient=redissonClient;
    }
}
五、最后,redissonnclient用于操作redis
@Configuration
public class RedissonConfig {
    @Bean(destroyMethod="shutdown")
    public RedissonClient getRedissonClient() throws IOException {
        ResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource("redisson.yml");
        Config config = Config.fromYAML(resource.getInputStream());
        return Redisson.create(config);
    }
}
  • redisson.yml
singleServerConfig:
  idleConnectionTimeout: 10000  #连接空闲超时,单位:毫秒 默认:10000
  connectTimeout: 10000  #连接超时,单位:毫秒。默认:10000
  timeout: 3000 #命令等待超时,单位:毫秒 默认:3000
  retryAttempts: 3 #命令失败重试次数
  retryInterval: 1500 #命令重试发送时间间隔,单位:毫秒
 # lockWatchdogTimeout: 30000 #监控锁的看门狗超时,单位:毫秒  该参数只适用于分布式锁的加锁请求中未明确使用leaseTimeout参数的情况。如果该看门口未使用lockWatchdogTimeout去重新调整一个分布式锁的lockWatchdogTimeout超时,那么这个锁将变为失效状态。这个参数可以用来避免由Redisson客户端节点宕机或其他原因造成死锁的情况。
  password: xxxxxxxxx
  clientName: null #客户端名称
  subscriptionsPerConnection: 5 #单个连接最大订阅数量
  address: "redis://ip:port"
  subscriptionConnectionMinimumIdleSize: 1 #从节点发布和订阅连接的最小空闲连接数
  subscriptionConnectionPoolSize: 50 #从节点发布和订阅连接池大小
  # 集群模式下不支持该选项
  database: 10
  dnsMonitoringInterval: 5000 #DNS监控间隔,单位:毫秒 在启用该功能以后,Redisson将会监测DNS的变化情况
  sslEnableEndpointIdentification: true #启用SSL终端识别,默认为true
threads: 0 #线程池数量 默认值: 当前处理核数量 * 2
nettyThreads: 0 #Netty线程池数量 默认值: 当前处理核数量 * 2 ,这个线程池数量是在一个Redisson实例内,被其创建的所有分布式数据类型和服务,以及底层客户端所一同共享的线程池里保存的线程数量。
codec: #编码 默认值:org.redisson.codec.JsonJacksonCodec,Redisson的对象编码类是用于将对象进行序列化和反序列化,以实现对该对象在Redis里的读取和存储
  ! {}
"transportMode": #传输模式 默认:TransportMode.NIO linux系统下可以使用RPOLL,性能高
  "NIO"

六、总结

通过spring-session集成shiro,我们可以实现用户权限控制,认证缓存,将session的存储位置由tomcat等web容器剥离至redis或者mysql中进行持久化,这样即使微服务中某台机器宕机,重启,也不会丢失用户信息,进一步提高系统的健壮性。同时,本例子只在在单机redis的情况下使用,各位可以进一步将redis扩展到redis集群,在redisson的帮助下,使用起来也非常方便

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号