shiro的session共享,持久化

2019-09-28 00:03:21

也是别人的项目。

不好意思加redis,那么自己加了个ehcache来实现。

下面的介绍依然没有直接写,也是读的别人的介绍,自己改装的


参考资料  shiro源码篇 - shiro的session共享,你值得拥有

  shiro的session创建session的查询、更新、过期、删除中,shiro对session的操作基本都讲到了,但还缺一个session共享没有讲解;session共享的原理其实在定义session管理一文已经讲过了,本文不讲原理,只看看shiro的session共享的实现。

  为何需要session共享

    如果是单机应用,那么谈不上session共享,session放哪都无所谓,不在乎放到默认的servlet容器中,还是抽出来放到单独的地方;

    也就是说session共享是针对集群(或分布式、或分布式集群)的;如果不做session共享,仍然采用默认的方式(session存放到默认的servlet容器),当我们的应用是以集群的方式发布的时候,同个用户的请求会被分发到不同的集群节点(分发依赖具体的负载均衡规则),那么每个处理同个用户请求的节点都会重新生成该用户的session,这些session之间是毫无关联的。那么同个用户的请求会被当成多个不同用户的请求,这肯定是不行的。

  如何实现session共享

    实现方式其实有很多,甚至可以不做session共享,具体有哪些,大家自行去查资料。本文提供一种方式:redis实现session共享,就是将session从servlet容器抽出来,放到redis中存储,所有集群节点都从redis中对session进行操作。

SessionDAO

  SessionDAO其实是用于session持久化的,但里面有缓存部分,具体细节我们往下看

  shiro已有SessionDAO的实现如下

  SessionDAO接口提供的方法如下

 View Code

    SessionDAO给出了从持久层(一般而言是关系型数据库)操作session的标准。

  AbstractSessionDAO提供了SessionDAO的基本实现,如下

 View Code

    SessionDao的基本实现,实现了SessionDao的create、readSession(具体还是依赖AbstractSessionDAO子类的doCreate、doReadSession实现);同时加入了自己的sessionId生成器,负责sessionId的操作。

  CachingSessionDAO提供了session缓存的功能,如下

 View Code

    是应用层与持久化层之间的缓存层,不用频繁请求持久化层以提升效率。重写了AbstractSessionDAO中的create、readSession方法,实现了SessionDAO中的update、delete、getActiveSessions方法,预留doUpdate和doDelele给子类去实现(doXXX方法操作的是持久层)

  MemorySessionDAO,SessionDAO的简单内存实现,如下

 View Code

    将session保存在内存中,存储结构是ConcurrentHashMap;项目中基本不用,即使我们不实现自己的SessionDAO,一般用的也是EnterpriseCacheSessionDAO。

  EnterpriseCacheSessionDAO,提供了缓存功能的session维护,如下

 View Code

    设置了默认的缓存管理器(AbstractCacheManager)和默认的缓存实例(MapCache),实现了缓存效果。从父类继承的持久化操作方法(doXXX)都是空实现,也就说EnterpriseCacheSessionDAO是没有实现持久化操作的,仅仅只是简单的提供了缓存实现。当然我们可以继承EnterpriseCacheSessionDAO,重写doXXX方法来实现持久化操作。

  总结下:SessionDAO定义了从持久层操作session的标准;AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的session缓存的功能,只需要设置相应的 CacheManager 即可;MemorySessionDAO直接在内存中进行session维护;而EnterpriseCacheSessionDAO提供了缓存功能的session维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap保存缓存的会话。因为shiro不知道我们需要将session持久化到哪里(关系型数据库,还是文件系统),所以只提供了MemorySessionDAO持久化到内存(听起来怪怪的,内存中能说成持久层吗)

shiro session共享

  共享实现

    shiro的session共享其实是比较简单的,重写CacheManager,将其操作指向我们的redis,然后实现我们自己的CachingSessionDAO定制缓存操作和缓存持久化。

    自定义CacheManager

      ShiroRedisCacheManager

 View Code

      ShiroRedisCache

 View Code

    自定义CachingSessionDAO

      继承EnterpriseCacheSessionDAO,然后重新设置其CacheManager(替换掉默认的内存缓存器),这样也可以实现我们的自定义CachingSessionDAO,但是这是优选吗;如若我们实现持久化,继承EnterpriseCacheSessionDAO是优选,但如果只是实现session缓存,那么CachingSessionDAO是优选,自定义更灵活。那么我们还是继承CachingSessionDAO来实现我们的自定义CachingSessionDAO

      ShiroSessionDAO

 View Code

    最后将ShiroSessionDAO实例赋值给SessionManager实例,再讲SessionManager实例赋值给SecurityManager实例即可

    具体代码请参考spring-boot-shiro

  源码解析

    底层还是利用Filter + HttpServletRequestWrapper将对session的操作接入到自己的实现中来,而不走默认的servlet容器,这样对session的操作完全由我们自己掌握。

    shiro的session创建中其实讲到了shiro中对session操作的基本流程,这里不再赘述,没看的朋友可以先去看看再回过头来看这篇。本文只讲shiro中,如何将一个请求的session接入到自己的实现中来的;shiro中有很多默认的filter,我会单独开一篇来讲shiro的filter,这篇我们先不纠结这些filter。

    OncePerRequestFilter中doFilter方法如下

 View Code

    上图中,我可以看到AbstractShiroFilter的doFilterInternal放中将request封装成了shiro自定义的ShiroHttpServletRequest,将response也封装成了shiro自定义的ShiroHttpServletResponse。既然Filter中将request封装了ShiroHttpServletRequest,那么到我们应用的request就是ShiroHttpServletRequest类型,也就是说我们对session的操作最终都是由shiro完成的,而不是默认的servlet容器。

    另外补充一点,shiro的session创建不是懒创建的。servlet容器中的session创建是第一次请求session(第一调用request.getSession())时才创建。shiro的session创建如下图

    此时,还没登录,但是subject、session已经创建了,只是subject的认证状态为false,说明还没进行登录认证的。至于session创建过程已经保存到redis的流程需要大家自行去跟,或者阅读我之前的博文

总结

  1、当以集群方式对外提供服务的时候,不做session共享也是可以的

    可以通过ip_hash的机制将同个ip的请求定向到同一台后端,这样保证用户的请求始终是同一台服务处理,与单机应用基本一致了;但这有很多方面的缺陷(具体就不详说了),不推荐使用。

  2、servlet容器之间做session同步也是可以实现session共享的

    一个servlet容器生成session,其他节点的servlet容器从此servlet容器进行session同步,以达到session信息一致。这个也不推荐,某个时间点会有session不一致的问题,毕竟同步过程受到各方面的影响,不能保证session实时一致。

  3、session共享实现的原理其实都是一样的,都是filter + HttpServletRequestWrapper,只是实现细节会有所区别;有兴趣的可以看下spring-session的实现细节。

  4、如果我们采用的spring集成shiro,其实可以将缓存管理器交由spring管理,相当于由spring统一管理缓存。

  5、shiro的CacheManager不只是管理session缓存,还管理着身份认证缓存、授权缓存,shiro的缓存都是CacheManager管理。但是身份认证缓存默认是关闭的,个人也不推荐开启。

  6、shiro的session创建时机是在登录认证之前,而不是第一次调用getSession()时。


  • 2017-09-11 16:34:14

    Nginx如何保留真实IP和获取前端IP

    squid,varnish以及nginx等,在做反向代理的时候,因为要代替客户端去访问服务器,所以,当请求包经过反向代理后,在代理服务器这里这个IP数据包的IP包头做了修改,最终后端web服务器得到的数据包的头部的源IP地址是代理服务器的IP地址,这样一来,后端服务器的程序给予IP的统计功能就没有任何意义,所以在做代理或集群的时候必须解决这个问题,这里,我以nginx做集群或代理的时候如何给后端web服务器保留(确切的说是传递)客户端的真实IP地址。

  • 2017-09-11 16:35:22

    ngx_http_realip_module使用详解

    网络上关于ngx_http_realip_module的文章千篇一律,全是在说怎么安装,最多贴一个示例配置,却没有说怎么用,为什么这么用,官网文档写得也十分简略,于是就自己探索了一下。

  • 2017-09-11 16:39:43

    基于Nginx dyups模块的站点动态上下线

    在分布式服务下,我们会用nginx做负载均衡, 业务站点访问某服务站点的时候, 统一走nginx, 然后nginx根据一定的轮询策略,将请求路由到后端一台指定的服务器上。

  • 2017-09-13 13:49:21

    Web性能测试:工具之Siege详解

    Siege是一款开源的压力测试工具,设计用于评估WEB应用在压力下的承受能力。可以根据配置对一个WEB站点进行多用户的并发访问,记录每个用户所有请求过程的相应时间,并在一定数量的并发访问下重复进行。siege可以从您选择的预置列表中请求随机的URL。所以siege可用于仿真用户请求负载,而ab则不能。但不要使用siege来执行最高性能基准调校测试,这方面ab就准确很多

  • 2017-09-14 10:18:25

    15分钟成为Git专家

    不管是以前使用过 Git 还是刚开始使用这个神奇的版本控制工具的开发者,阅读了本文以后都会收获颇丰。如果你是应一名有经验的 GIT 使用者,你会更好的理解 checkout -> modify -> commit 这个过程。如果你刚开始使用 Git,本文将给你一个很好的开端。

  • 2017-09-28 16:42:57

    Linux vmstat命令实战详解

    vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况。这个命令是我查看Linux/Unix最喜爱的命令,一个是Linux/Unix都支持,二是相比top,我可以看到整个机器的CPU,内存,IO的使用情况,而不是单单看到各个进程的CPU使用率和内存使用率(使用场景不一样)。

  • 2017-10-13 16:21:29

    Activity的四种launchMode

    launchMode在多个Activity跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity实例,是否重用已存在的Activity实例,是否和其他Activity实例公用一个task里。这里简单介绍一下task的概念,task是一个具有栈结构的对象,一个task可以管理多个Activity,启动一个应用,也就创建一个与之对应的task。