-
2020-03-31 10:10:26
伴随网站业务规模和访问量的逐步发展,原本由单台服务器、单个域名的迷你网站架构已经无法满足发展需要。
此时我们可能会购买更多服务器,并且启用多个二级子域名以频道化的方式,根据业务功能将网站分布部署在独立的服务器上;或通过负载均衡技术(如:DNS轮询、Radware、F5、LVS等)让多个频道共享一组服务器。
OK,头脑中我们已经构思了这样的解决方案,不过进入深入开发后新的技术问题又随之而来:
我们把网站程序分布部署到多台服务器上,而且独立为几个二级域名,由于Session受实现原理的局限(PHP中Session默认以文件的形式保存在本地服务器的硬盘),使得我们的网站用户不得不经常在几个频道间来回输入用户名、密码登入,导致用户体验大打折扣;另外,原本程序可以直接从用户Session变量中读取的资料(如:昵称、积分、登入时间等),因为无法跨服务器同步更新Session 变量,迫使开发人员必须实时读写数据库,从而增加了数据库的负担。
于是,解决网站跨服务器之间的Session共享方案需求变得迫切起来,最终催生了多种解决方案,下面列举4种较为可行的方案进行对比探讨:
1. 基于NFS的Session共享
NFS是Net FileSystem的简称,最早由Sun公司为解决Unix网络主机间的目录共享而研发。
这个方案实现最为简单,无需做过多的二次开发,仅需将共享目录服务器mount到各频道服务器的本地session目录即可,缺点是NFS依托于复杂的安全机制和文件系统,因此并发效率不高,尤其对于session这类高并发读写的小文件,会由于共享目录服务器的io-wait过高,最终拖累前端WEB应用程序的执行效率。
2. 基于数据库的Session共享
首选当然是大名鼎鼎的Mysql数据库,并且建议使用内存表Heap,提高session操作的读写效率。这个方案的实用性比较强,相信大家普遍在使用,它的缺点在于session的并发读写能力取决于Mysql数据库的性能,同时需要自己实现session淘汰逻辑,以便定时从数据表中更新、删除 session记录,当并发过高时容易出现表锁,虽然我们可以选择行级锁的表引擎,但不得不否认使用数据库存储Session还是有些杀鸡用牛刀的架势。
3. 基于Cookie的Session共享
这个方案我们可能比较陌生,但它在大型网站中还是比较普遍被使用。原理是将全站用户的Session信息加密、序列化后以Cookie的方式,统一种植在根域名下(如:.host.com),利用浏览器访问该根域名下的所有二级域名站点时,会传递与之域名对应的所有Cookie内容的特性,从而实现用户的Cookie化Session 在多服务间的共享访问。
这个方案的优点无需额外的服务器资源;缺点是由于受http协议头信心长度的限制,仅能够存储小部分的用户信息,同时Cookie化的 Session内容需要进行安全加解密(如:采用DES、RSA等进行明文加解密;再由MD5、SHA-1等算法进行防伪认证),另外它也会占用一定的带宽资源,因为浏览器会在请求当前域名下任何资源时将本地Cookie附加在http头中传递到服务器。
4. 基于Memcache的Session共享
Memcache由于是一款基于Libevent多路异步I/O技术的内存共享系统,简单的Key + Value数据存储模式使得代码逻辑小巧高效,因此在并发处理能力上占据了绝对优势,目前本人所经历的项目达到2000/秒 平均查询,并且服务器CPU消耗依然不到10%。
另外值得一提的是Memcache的内存hash表所特有的Expires数据过期淘汰机制,正好和Session的过期机制不谋而合,降低了过期Session数据删除的代码复杂度,对比“基于数据库的存储方案”,仅这块逻辑就给数据表产生巨大的查询压力。
总结:
关于SESSION共享
不过由于nfs的锁机制,在高并发情况下会有效率问题,比如想用作共享session的目录的话,可能nfs会变成性能的瓶颈,这里,有这么篇文章,写得很具体,基本就是分文件夹存储的形式:
主要即:
1. 基于NFS的Session共享2. 基于数据库的Session共享
3. 基于Cookie的Session共享
4. 基于Memcache的Session共享
nfs的瓶颈也许就在nfs锁机制上,数据库想必是网络传输和磁盘io,cookie也许会存在一些安全问题,否则就是会需要加密解密的机制,耗费cpu和网络传输,四种最快的想必是memcache的共享,并且没有安全问题
转自:http://blog.chinaunix.net/uid-26363964-id-3049097.html
https://www.cnblogs.com/cyw080/archive/2009/11/17/1604272.html
更多相关内容 -
nginx 负载均衡 多站点共享Session
2021-01-10 14:30:28多站点共享Session常见的作法有: •使用.net自动的状态服务(Asp.net State Service); •使用.net的Session数据库; •使用Memcached。 •使用Cookie方式实现多个站点间的共享(这种方式只限于几个站点都在同一... -
跨域共享session (实现http跳转https 共享session)
2019-07-13 02:18:48NULL 博文链接:https://justcoding.iteye.com/blog/747398 -
多台服务器之间共享session
2019-04-15 01:37:15NULL 博文链接:https://eleopard.iteye.com/blog/1767816 -
springboot通过redis共享session
2019-05-01 01:55:16NULL 博文链接:https://xnbhnly.iteye.com/blog/2432706 -
Tomcat7共享session
2018-04-09 17:33:12多个服务器部署Tomcat共享Session的方法,自己亲身测试 -
springboot+redis共享session(Demo)
2018-09-18 15:22:30基于maven构建的springboot工程,结合redis实现session共享的源码 -
Tomcat 同一服务器上 不同web项目共享session的
2019-03-30 01:08:02NULL 博文链接:https://rainbow702.iteye.com/blog/1312307 -
SpringBoot开发案例 分布式集群共享Session详解
2020-08-25 20:06:46主要介绍了SpringBoot开发案例 分布式集群共享Session详解,在分布式系统中,为了提升系统性能,通常会对单体项目进行拆分,分解成多个基于功能的微服务,可能还会对单个微服务进行水平扩展,保证服务高可用,需要的... -
集群共享session;shiro实现session共享;springboot实现redis共享session;
2022-03-19 18:48:24shiro实现共享session;springboot集成redis实现共享session;集群环境下shiro共享session;shiro实现session共享shiro实现共享session;springboot集成redis共享session;集群环境下shiro共享session
一、实现session共享
1. 聊聊session共享
如果是单机应用,session共享意义不大。使用默认的session缓存,还是存放到第三方应用缓存中,都可以。当然极端情况下,如果服务器内存非常小等极特殊情况下可能需要第三方缓存的。
session共享是针对集群(或分布式、或分布式集群)的情况下采用;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。
2. shiro实现session共享(使用redis方式实现)
实现共享session是比较简单的,换一种说明你就能明白。大家都会增、删、改、查,session的操作就是增删改查的过程,只不过默认是缓存到servlet容器中,咱们要将数据转移到redis,来实现它的增删改查。这样在集群环境中,大家都访问这个redis,也就实现了共享session.
下面首先要了解下 shiro创建和缓存session的过程。
- DefaultWebSessionManager.java
这个类是shiro的session管理类,我们在管理session生命周期时候,也该从这入手。 - AbstractSessionDAO.java
这个类是session创建、保存、删除、更新操作的代码,默认会保存的servlet容器中。我们只需要继承这个类,并覆写对应代码即可实现session从redis中增删改查。下面来看看AbstractSessionDAO类官方的源码,其实也是增删改查:
protected abstract Serializable doCreate(Session session); public void delete(Session session) { uncache(session); doDelete(session); } public void update(Session session) throws UnknownSessionException { doUpdate(session); if (session instanceof ValidatingSession) { if (((ValidatingSession) session).isValid()) { cache(session, session.getId()); } else { uncache(session); } } else { cache(session, session.getId()); } } protected abstract Session doReadSession(Serializable sessionId);
从源码中可以看出,共享sessionId说白了就是改变增删改查保存的位置。
默认session是保存的servlet缓存中,进行增删改查,现在咱们覆写方法,把增删改查的数据源改为redis。- 新建实现类RedisSessionDao,并继承AbstractSessionDAO
import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; import org.apache.shiro.session.mgt.eis.CachingSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.io.Serializable; import java.util.Collection; import java.util.concurrent.TimeUnit; /** * @Author wangy * @create 2022/3/19 13:47 * @Description 实现sessionDao,从而将session信息保存到redis中,达到集群环境共享session目的 */ @Component public class RedisSessionDao extends AbstractSessionDAO { /** * Session超时时间,单位为毫秒 当前设置半个小时 */ private long expireTime = 1800000; /** * 注入Redis操作类 */ @Autowired private RedisTemplate redisTemplate; /** * 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合 * @return */ @Override public Collection<Session> getActiveSessions() { return redisTemplate.keys("*"); } /** * 新增和保存session到redis中 * @param session * @return */ @Override protected Serializable doCreate(Session session) { Serializable sessionId = this.generateSessionId(session); this.assignSessionId(session, sessionId); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS); return sessionId; } /** * 读取redis中的sessioin * @param sessionId * @return */ @Override protected Session doReadSession(Serializable sessionId) { if (sessionId == null) { return null; } Session session = (Session) redisTemplate.opsForValue().get(sessionId); return session; } /** * 用户请求接口,然后修改session的有效期 * @param session */ @Override public void update(Session session) throws UnknownSessionException { if (session == null || session.getId() == null) { return; } //设置超时时间,这个是毫秒 session.setTimeout(expireTime); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS); } /** * session到期后删除session,比如说退出登录 logout * @param session */ @Override public void delete(Session session) { if (null == session) { return; } redisTemplate.opsForValue().getOperations().delete(session.getId()); } }
这个类的代码中实现了对session的增删改查操作,大家应该到这比较容易理解了。
- 最后将实现类应用到DefaultWebSessionManager中。
在自己的ShiroConfig配置类型,如下代码:
@Bean public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCachingSessionDao) { //新建security并设置realm、SessionManager DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(new AdminAuthorizingRealm()); //新建SessionManager并设置SessionDao(session的获取途径) DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisCachingSessionDao); //应用sessionManager securityManager.setSessionManager(sessionManager); return securityManager; }
将自己的实现类redisCachingSessionDao,通过代码set到sessionManager中。
这样自己创建session后,可以从redis中查到自己的session数据了,共享session完成了,简单吧!3. 共享缓存实现
共享session已经实现了,集群环境中能够访问相同的session数据源,但是shiro仍会有一些数据会缓存在servlet容器中,这样集群环境会出现一些其他的shiro配置数据各自用的还是各自的,出现各种各样问题。所以后面还需要解决 共享缓存的问题。
用到下面一些类:- CacheManager.java
这个类是shiro的缓存接口,默认会使用servlet容器的来充当缓存。
我们看下源码,这个类定义了获取缓存的方法。
package org.apache.shiro.cache; public interface CacheManager { <K, V> Cache<K, V> getCache(String var1) throws CacheException; }
- Cache.java
这个类是获取缓存方式的,通过getCache得到Cache的实现类,默认获取到的是shiro自己容器中,看下源码分析:
public interface Cache<K, V> { V get(K var1) throws CacheException; V put(K var1, V var2) throws CacheException; V remove(K var1) throws CacheException; void clear() throws CacheException; int size(); Set<K> keys(); Collection<V> values(); }
同样道理,咱们继承这个类,并覆写这个类的增删改查,从缓存到servlet,转移到redis中。
下面咱们定义自己的实现类
3. ShiroRedisCacheimport org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Collection; import java.util.Set; import java.util.concurrent.TimeUnit; /** * @Author wangy * @create 2022/3/19 11:55 * @Description 缓存的实现类 shiro所有的缓存数据,都会存到redis中 */ @Component public class ShiroRedisCache<K,V> implements Cache<K,V>{ /** * redis操作类 */ @Autowired private RedisTemplate<K,V> redisTemplate; /** * 定义缓存生效时间 为半个小时 */ private long expireTime = 1800; /** * 查询 操作 */ @Override public V get(K k) throws CacheException { return redisTemplate.opsForValue().get(k); } /** * 新增 操作 */ @Override public V put(K k, V v) throws CacheException { redisTemplate.opsForValue().set(k,v,expireTime, TimeUnit.SECONDS); return null; } /** * 删除 操作 */ @Override public V remove(K k) throws CacheException { V v = redisTemplate.opsForValue().get(k); redisTemplate.opsForValue().getOperations().delete(k); return v; } @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; } }
然后再次继承CacheManger,实现getCache方法
4. ShiroRedisCacheManagerimport org.apache.shiro.cache.Cache; import org.apache.shiro.cache.CacheException; import org.apache.shiro.cache.CacheManager; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** 1. @Author wangy 2. @create 2022/3/19 11:52 3. @Description 实现缓存管理manager,所有缓存从redis中取数据 */ @Component public class ShiroRedisCacheManager implements CacheManager { /** * 注入自己的redisCache */ @Resource private Cache shiroRedisCache; /** * 覆写方法 */ @Override public <K, V> Cache<K, V> getCache(String s) throws CacheException { return shiroRedisCache; } }
最后将两个类set到Shrio配置中。
4. set到shiro配置类中@Bean public DefaultWebSecurityManager securityManager(RedisCachingSessionDao redisCahingSessionDao, ShiroRedisCacheManager shiroRedisCacheManager) { //新建security并设置realm、CacheManager、SessionManager DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(new AdminAuthorizingRealm()); //如果使用redis共享session,这个必须设置,因为集群之中 session要共享,同样一些缓存的数据也要共享,比如shiro缓存的数据 securityManager.setCacheManager(shiroRedisCacheManager); //新建SessionManager并设置SessionDao(session的获取途径) DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionDAO(redisCahingSessionDao); securityManager.setSessionManager(sessionManager); return securityManager; }
上面代码通过securityManager设置cacheManager属性来使用redis缓存方式。
4. 总结
- 集群环境中shiro实现完共享session缓存,同样也要实现共享缓存,才能保证系统完美运行。
- 共享缓存不一定要用redis,大家通过覆写方法可以用mongodb、mysql、其他缓存工具等等都是可以实现的,当然要考虑效率和性能。
- 集群环境中负载均衡还可以通过ip_hash的机制将同个ip的请求定向到同一台后端,这样保证用户的请求始终是同一台服务处理,与单机应用基本一致了;但这有很多方面的缺陷(比如在同一个局域网环境下,都会分配到同一个ip,就失去负载均衡作用了)
nginx负载均衡可以参考本人博文:https://blog.csdn.net/wangyue23com/article/details/108197650
- DefaultWebSessionManager.java
-
Tomcat7集群实现共享session,已配置好。
2018-04-17 15:09:23已经配置好,只需要自己更改redis的ip地址就可以使用,请测可以使用。 -
laravel实现共享session案例.docx
2020-12-02 11:57:07自己亲自试验,亲测有效。实现父子网站共享session。可实现单点登录的效果,具体还是发挥各位大佬的才能了,此文只是实现共享session的亲测案例 -
android与asp.net服务端共享session的方法详解
2020-08-29 12:04:27主要给大家介绍了关于android与asp.net服务端如何共享session的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋们下面随着小编来一起学习学习下吧。 -
PHP简单实现HTTP和HTTPS跨域共享session解决办法
2020-10-24 04:35:48主要介绍了PHP简单实现HTTP和HTTPS跨域共享session解决办法,本文讲解的方法相对简单,需要的朋友可以参考下 -
nginx+tomcat实现负载均衡,共享session的两种方式
2018-04-16 14:35:35nginx+tomcat实现负载均衡,共享session的两种方式: 1.使用Redis共享session 2.使用tomcat的组播功能。 -
Nginx负载均衡条件下Redis共享Session测试
2019-02-19 14:20:16Nginx负载均衡条件下Redis共享Session测试,实现负载均衡下两个服务器的项目可以共享Session -
实现Asp与Asp.Net共享Session的方法
2020-10-25 12:40:09主要介绍了实现Asp与Asp.Net共享Session的方法,需要的朋友可以参考下 -
session共享方案(tomcat8+redis共享session)
2018-07-25 20:28:15session共享方案(tomcat8+redis共享session) 一步一步详细指引 -
tomcat8共享session之redis缓存
2019-02-11 11:51:40本插件适用tomcat8集成redis缓存。用于tomcat集群共享session之用。 -
springboot2.0.3 的redis共享session的demo
2018-09-16 10:28:37springboot2.0.3 的redis共享session的demo,可以学习一下session共享 -
redis共享session
2017-11-27 12:02:55redis共享session所需要的jar包,包括(commons-pool2-2.0.jar、jedis-2.6.0.jar、tomcat-redis-session-manager-sentinel-support-1.2.jar) -
Springboot实现多服务器session共享
2020-08-26 01:04:48主要为大家详细介绍了Springboot实现多服务器session共享,具有一定的参考价值,感兴趣的小伙伴们可以参考一下 -
利用redis实现session共享
2018-06-29 15:22:31利用Nginx进行分布式部署,并使用redis实现session共享,https://blog.csdn.net/qq_34742298/article/details/80857173 -
tomcat8集群实现session共享,内含session共享包
2018-04-17 15:17:38完整tomcat8内含session共享包,亲自测试可以使用,有问题可以私聊。 -
如何解决asp.net负载均衡时Session共享的问题
2020-10-17 18:10:27主要介绍了解决asp.net负载均衡时Session共享的问题,详细的介绍了ASP.Net session存储方式以及如何实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
nginx实现session共享
2017-07-20 09:52:36兼容Tomcat7 与tomcat8 +memcached做session共享 , 解决了tomcat7与Tomcat8的 java.lang.NoSuchFieldError: attributes 错误。 -
Zend Framework实现多服务器共享SESSION数据的方法
2020-12-18 18:38:02本文实例讲述了Zend Framework实现多服务器共享SESSION数据的方法。分享给大家供大家参考,具体如下: 一、问题起源 大型网站通常有多个服务器,并且使用多个二级域名。这样一台服务器产生的session就不能为所有... -
SpringBoot中实现分布式的Session共享的详细教程
2020-08-19 01:00:46主要介绍了SpringBoot中实现分布式的Session共享,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下 -
shiro框架---多项目登录访问共享session的实现
2018-08-04 17:55:57shiro框架—多项目登录访问共享session的实现 公司需要这样的需求: 有两个项目master 主项目、suiteone 项目,两个项目各自由shiro 安全框架管理,当不能登录时,都无法访问,但当登录了其中一个,再访问另...shiro框架—多项目登录访问共享session的实现
公司需要这样的需求:
有两个项目
master
主项目、suiteone
项目,两个项目各自由shiro
安全框架管理,当不能登录时,都无法访问,但当登录了其中一个,再访问另一个的时候不再需要登录即可访问。
如果想看为什么需要共享session
,可以去看我这篇文章。shiro框架—关于多项目之间验证为什么需要共享session关于实现多项目共享session的逻辑介绍
其实在上边的链接里我已经说明了,但是怕大家不去看,所以我就又复制到了这里:
先来说一下我的理解
,如下:
上图中master
项目为主项目,登录页即在这个项目中,suiteone
、suitetwo
为两个从项目,当两个从项目有请求时,如果没有登录的时候,都会打到master
项目的登录页上。共享session
采用的是redis
存储。上图的步骤如下:
- 浏览器请求
master
项目,第一次请求的时候,也是会带着浏览器中的cookie
去请求,当然第一次去redis
里肯定找不到对应的session
,会通过⑤进入到登录页。 - 当在登录页输入完正确的账号密码后,才能登录成功,否则仍会回到⑤。
- 在这一步的时候,会将登录成功后的
session
,根据它,将生成sessionId串
,并传到前端浏览器中,浏览器以cookie
存储。 - 同时将第③步中生成的
session
存储到redis
中。 - 当前这里,不只是当登录失败的时候,会进入到登录页中,当浏览器长时间没有访问后台(每次浏览器访问后台,其实都会刷新
session
的过期时间expireTime
),导致session
超过时,也会进入到该步中。 - 当浏览器请求
suiteone
、suteTwo
这两个从项目时,肯定也是将当前浏览器中的所有的cookie
设置到request headers
请求头中。 - 根据传入的
sessionId串
到共享的redis
存储中匹配。 - 如果匹配不到,则会跳转到
master
项目的登录页,如果匹配成功,则会访问通过。
以上描述的并不难,大家也都会想到,那么如何将
扯了这么多的淡
真正更简单的,落地实现才是大家关注的。多项目通过redis共享session的步骤
关于该项目已在
GitHub
上传,有兴趣的可以下载下来瞧瞧,GitHub项目地址1、
shiro
共享session
的切入点,下图是shiro的验证流程2、配置过程
说一下实现
shiro
项目共享session
的配置步骤:(1)项目多添加
redis
依赖。<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.3.6.RELEASE</version> </dependency>
(2)配置
application.properties
文件###主项目写成自己项目的登录页面路径,从项目必须写完整的主项目登录页面url shiro.loginUrl=/login ###主从项目的下边名字必须一致 shiro.jessionid=sessionId ###redis连接配置 spring.redis.host=192.168.1.160 spring.redis.port=6379 spring.redis.password=znxd
(3)重写
session
增删改查,与redis接入package microservice.sc.shiro; import org.apache.shiro.session.Session; import org.apache.shiro.session.UnknownSessionException; import org.apache.shiro.session.mgt.eis.AbstractSessionDAO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.io.Serializable; import java.util.Collection; import java.util.concurrent.TimeUnit; /** * Created by Administrator on 2018/7/31. */ @Service public class RedisSessionDao extends AbstractSessionDAO { // Session超时时间,单位为毫秒 private long expireTime = 120000; @Autowired private RedisTemplate redisTemplate;// Redis操作类,对这个使用不熟悉的,可以参考前面的博客 public RedisSessionDao() { super(); } public RedisSessionDao(long expireTime, RedisTemplate redisTemplate) { super(); this.expireTime = expireTime; this.redisTemplate = redisTemplate; } @Override // 更新session public void update(Session session) throws UnknownSessionException { System.out.println("===============update================"); if (session == null || session.getId() == null) { return; } session.setTimeout(expireTime); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS); } @Override // 删除session public void delete(Session session) { System.out.println("===============delete================"); if (null == session) { return; } redisTemplate.opsForValue().getOperations().delete(session.getId()); } @Override // 获取活跃的session,可以用来统计在线人数,如果要实现这个功能,可以在将session加入redis时指定一个session前缀,统计的时候则使用keys("session-prefix*")的方式来模糊查找redis中所有的session集合 public Collection<Session> getActiveSessions() { System.out.println("==============getActiveSessions================="); return redisTemplate.keys("*"); } @Override// 加入session protected Serializable doCreate(Session session) { System.out.println("===============doCreate================"); Serializable sessionId = this.generateSessionId(session); this.assignSessionId(session, sessionId); redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.MILLISECONDS); return sessionId; } @Override// 读取session protected Session doReadSession(Serializable sessionId) { System.out.println("==============doReadSession================="); if (sessionId == null) { return null; } return (Session) redisTemplate.opsForValue().get(sessionId); } public long getExpireTime() { return expireTime; } public void setExpireTime(long expireTime) { this.expireTime = expireTime; } public RedisTemplate getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } }
(4)将重写的
RedisSessionDao
接入到shiro
中的sessionManager
@Bean public RedisSessionDao getRedisSessionDao(){ return new RedisSessionDao(); } /** * @see DefaultWebSessionManager * @return */ @Bean(name="sessionManager") public DefaultWebSessionManager defaultWebSessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); //sessionManager.setCacheManager(cacheManager()); sessionManager.setGlobalSessionTimeout(43200000); //12小时 sessionManager.setDeleteInvalidSessions(true); //关键在这里 sessionManager.setSessionDAO(getRedisSessionDao()); sessionManager.setSessionValidationSchedulerEnabled(true); sessionManager.setDeleteInvalidSessions(true); sessionManager.setSessionIdCookie(getSessionIdCookie()); return sessionManager; }
(5)将
sessionManager
注入到securityManager
/** * @see org.apache.shiro.mgt.SecurityManager * @return */ @Bean(name="securityManager") public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(userRealm()); //manager.setCacheManager(cacheManager()); manager.setSessionManager(defaultWebSessionManager()); return manager; }
(6)设置
登录页的地址
和cookie名字
首先引入我们刚才
application.properties
文件中的内容:@Value("${shiro.loginUrl}") private String masterLoginUrl; @Value("${shiro.jessionid}") private String jessionId;
然后注入到配置中:
@Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter(){ ShiroFilterFactoryBean bean = new MShiroFilterFactoryBean(); //指向自定义过滤器,自定义过滤器对js/css等忽略 bean.setSecurityManager(securityManager()); //在这里设置登录页 bean.setLoginUrl(masterLoginUrl); Map<String, Filter>filters = new LinkedHashMap<>(); filters.put("anon", new AnonymousFilter()); bean.setFilters(filters); //shiro配置过滤规则少量的话可以用hashMap,数量多了要用LinkedHashMap,保证有序,原因未知 Map<String, String> chains = new LinkedHashMap<>(); chains.put("/login","anon"); chains.put("/loginForm","anon"); chains.put("/**", "authc"); bean.setFilterChainDefinitionMap(chains); return bean; }
/** * 给shiro的sessionId默认的JSSESSIONID名字改掉 * @return */ @Bean(name="sessionIdCookie") public SimpleCookie getSessionIdCookie(){ SimpleCookie simpleCookie = new SimpleCookie(jessionId); return simpleCookie; }
注意,如果值注入不进来,则看一下当前的
shiro
配置文件里的LifecycleBeanPostProcessor
的注入是否为static
方法,如下:/** * 该类如果不设置为static,@Value注解就无效,原因未知 * @return */ @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); }
(7)测试
我的项目如下:
如下图所示,我们是springboot+dubbo+zookeeper
的项目:
分别启动30000端口
的主项目master
项目、300001端口的
从项目suiteone
项目,启动后,- 首先访问主项目
http://localhost:30000
肯定进入登录页。 - 当再访问
http://localhost:30001/test
,因为没有登录直接被重定向到http://localhost:30000
的登录页上,即主项目的登录页。 - 当在当前登录页上登录成功后,再次访问
http://localhost:30001/test
,该接口即返回了数值。 - 当我在主项目的
http://localhost:30000
中退出登录,再次访问http://localhost:30001/test
,同样重定向了登录页。 - 应该能确定实现了
session
共享。
具体如下图:
登录页面,在这时访问30001
也会进入下边这个页面:
登录成功后:
登录成功后访问30001
的接口,即可以访问成功
配置完成,如有问题请及时留言。
- 浏览器请求