最新公告
  • 新注册用户请前往个人中心绑定邮箱以便接收相关凭证邮件!!!点击前往个人中心
  • Shiro应用篇(二):Shiro结合Redis实现分布式环境下的Session共享

    什么是Session?
    我们都知道HTTP协议(1.1)是无状态的,所以服务器在需要识别用户访问的时候,就要做相应的记录用于跟踪用户操作,这个实现机制就是Session。当一个用户第一次访问服务器的时候,服务器就会为用户创建一个Session,每个Session都有一个唯一的SessionId(应用级别)用于标识用户。
    Session通常不会单独出现,因为请求是无状态的,那么我们必须让用户在下次请求时带上服务器为其生成的Session的ID,通常的做法时使用Cookie实现(当然你要非要在请求参数中带上SessionId那也不是不行)。请求返回时会向浏览器的Cookie中写入SessionID,通常使用的键是JSESSIONID,这样下次用户再请求这台服务器时,服务器就能从Cookie中取出SessionId识别出该次请求的用户是谁。
    左边红框部分是Cookie列表,当前服务器是:localhost:28080。右边红框部分从左到右依次是Cookie的键、值、主机、路径和过期时间。路径为/时表示全站有效,最后一个过期时间未设置的话是默认值为Session,表示浏览器关闭时该Cookie失效。我们也可以为Cookie指定过期时间,以做到会话保持。
    什么是Session共享
    通过Session和Cookie,我们使得无状态的HTTP协议间接的变成了有状态的了,可以实现保持登录,存储用户信息,购物车等等功能。但是随着服务访问人数的增多,单台服务器已经不足以应付所有的请求了,必须部署集群环境。但是随着集群环境的出现,追踪用户状态的问题又开始出现问题,之前用户在A服务器登录,A服务器保存了用户信息,但是下一次请求发送到B服务器去了,这时候B服务器是不知道用户在A服务器登录的事情的,它虽然也能拿到用户请求Cookie中的SessionId,但是在B服务根据这个SessionId找不到对应的Session,B服务器就会认为用户没有登录,需要用户重新登录,这对用户来说是没办法接受的。
    这时候常见的有两种方式解决这个问题,第一种是让这个用户所有的请求都发送到A服务器,比如根据IP地址做一些列算法将所有用户分配到不同的服务器上去,让每个用户只访问其中的一台服务器。这种做法可行,但是后续也会产生其它问题,更好的做法是第二种,将所有的服务器上的Session都做成共享的,A服务能拿到B服务器上的所有Session,同理B服务器也能获取A服务器所有的Session,这样上面的问题就不存在了。
    Shiro结合Redis实现Session共享
    上一篇已经通过Shiro实现了用户登录和权限管理,Shiro的登录也是基于Session的,默认情况下Session是保存在内存中。既然要做Session共享,那么肯定是将Session抽取出来,放到一个多个服务器都能访问到的地方。
    在集群环境下,我们仅仅需要继承AbstractSessionDAO,实现一下Session的增删改查等几个方法就可以很方便的实现Session共享,Shiro已经将完整的流程都做好了。这里涉及到的设计模式是模板方法模式,我们仅需要参与部分业务就可以完善整个流程了,当然我们不参与这部分流程的话,Shiro也有默认的实现方式,那就是将Session管理在当前应用的内存中。
    具体的Session管理(共享)怎么实现由我们自己决定,可以存放在数据库,也可以通过网络传输,甚至可以通过IO流写入文件都行,但就性能来讲,我们一般都将Session放入Redis中。Redis大法好!YES~
    1)自定义RedisSessionDAO
    理解了原理之后就很容易办事了,继承AbstractSessionDAO后实现Session增删改查的几个方法,然后再分布式系统中所有的项目再需要存储或获取Session时都会走Redis操作,这样就做到了集群环境的Session共享了。代码非常简单:
    @Component
    public class RedisSessionDao extends AbstractSessionDAO {
        @Value(“${session.redis.expireTime}”)
        private long expireTime;
        @Autowired
        private RedisTemplate redisTemplate;
        @Override
        protected Serializable doCreate(Session session) {
            Serializable sessionId = this.generateSessionId(session);
            this.assignSessionId(session, sessionId);
            redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.SECONDS);
            return sessionId;
        }
        @Override
        protected Session doReadSession(Serializable sessionId) {
            return sessionId == null ? null : (Session) redisTemplate.opsForValue().get(sessionId);
        }
        @Override
        public void update(Session session) throws UnknownSessionException {
            if (session != null && session.getId() != null) {
                session.setTimeout(expireTime * 1000);
                redisTemplate.opsForValue().set(session.getId(), session, expireTime, TimeUnit.SECONDS);
            }
        }
        @Override
        public void delete(Session session) {
            if (session != null && session.getId() != null) {
                redisTemplate.opsForValue().getOperations().delete(session.getId());
            }
        }
        @Override
        public Collection<Session> getActiveSessions() {
            return redisTemplate.keys(“*”);
        }

    }

    配置文件中添加上面用到的配置
    ###redis连接配置
    spring.redis.host=localhost
    spring.redis.port=6379
    spring.redis.password=foobared
    ### Session过期时间(秒)
    session.redis.expireTime=3600
    2)注入RedisSessionDao
    上面只是我们自己实现的管理Session的方式,现在需要将其注入SessionManager中,并设置过期时间等相关参数。
    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDao redisSessionDao) {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    sessionManager.setGlobalSessionTimeout(expireTime * 1000);
    sessionManager.setDeleteInvalidSessions(true);
    sessionManager.setSessionDAO(redisSessionDao);
    sessionManager.setSessionValidationSchedulerEnabled(true);
    sessionManager.setDeleteInvalidSessions(true);
    /**
     * 修改Cookie中的SessionId的key,默认为JSESSIONID,自定义名称
     */
    sessionManager.setSessionIdCookie(new SimpleCookie(“JSESSIONID”));
    return sessionManager;

    }

    再将SessionManager注入Shiro的安全管理器SecurityManager中,前面说过,我们围绕安全相关的所有操作,都需要与SecurityManager打交道,这位才是Shiro中真正的老大哥。
    @Bean
    public SecurityManager securityManager(UserAuthorizingRealm userRealm, RedisSessionDao redisSessionDao) {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(userRealm);
    // 取消Cookie中的RememberMe参数
    securityManager.setRememberMeManager(null);
    securityManager.setSessionManager(defaultWebSessionManager(redisSessionDao));
    return securityManager;
    }
    OK,至此基于Redis实现的Session共享就完成了,是不是简单得不可思议。注意:基于网络传输的对象请实现Serializable序列化接口,比如User类。
    3)测试
    将这套代码用不同的端口跑两套服务(理论上跑多少套都可以只要你的配置够用),访问两台服务器获取用户信息的接口,未登录状态毫无疑问都会跳到登录页去:
    在任意一台服务器上调用登录接口登录:
    登录成功后再次分别访问两台服务器获取用户信息的接口:
    如此,分布式环境Session共享完美实现。最后继续放上项目代码,代码还是很早之前的,部分代码为了配合此篇笔记经过修改整理后上传。
    本站所有文章均由网友分享,仅用于参考学习用,请勿直接转载,如有侵权,请联系网站客服删除相关文章。若由于商用引起版权纠纷,一切责任均由使用者承担
    极客文库 » Shiro应用篇(二):Shiro结合Redis实现分布式环境下的Session共享

    常见问题FAQ

    如果资源链接失效了怎么办?
    本站用户分享的所有资源都有自动备份机制,如果资源链接失效,请联系本站客服QQ:2580505920更新资源地址。
    如果用户分享的资源与描述不符怎么办?
    可以联系客服QQ:2580505920,如果要求合理可以安排退款或者退赞助积分。
    如何分享个人资源获取赞助积分或其他奖励?
    本站用户可以分享自己的资源,但是必须保证资源没有侵权行为。点击个人中心,根据操作填写并上传即可。资源所获收益完全归属上传者,每周可申请提现一次。
    如果您发现了本资源有侵权行为怎么办?
    及时联系客服QQ:2580505920,核实予以删除。

    参与讨论

    • 184会员总数(位)
    • 3737资源总数(个)
    • 0本周发布(个)
    • 0 今日发布(个)
    • 572稳定运行(天)

    欢迎加入「极客文库」,成为原创作者从这里开始!

    立即加入 了解更多
    成为赞助用户享有更多特权立即升级