本篇是Shiro系列第二篇,使用Shiro基于Redis实现分布式环境下的Session共享。在讲Session共享之前先说一下为什么要做Session共享。

首发地址:https://www.guitu18.com/post/2019/07/28/44.html


为什么要做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~

自定义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

注入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类。

测试

将这套代码用不同的端口跑两套服务(理论上跑多少套都可以只要你的配置够用),访问两台服务器获取用户信息的接口,未登录状态毫无疑问都会跳到登录页去:

在任意一台服务器上调用登录接口登录:

登录成功后再次分别访问两台服务器获取用户信息的接口:

如此,分布式环境Session共享完美实现。最后继续放上项目代码,代码还是很早之前的,部分代码为了配合此篇笔记经过修改整理后上传。

Gitee:https://gitee.com/guitu18/ShiroDemo

GitHub:https://github.com/guitu18/ShiroDemo


本篇结束,简直不要太简单是不是,其实这主要是因为大部分工作Shiro都帮我们做了,细节的东西都被Shiro隐藏起来,我们仅仅需要添加一些简单的配置就可以实现强大的功能,这就是框架的好处。

但是作为一个程序员,仅仅调用一个方法或者添加一个注解就实现了一套很强大的功能,而我们却看不到一个if判断和for循环的时候心里应该是非常不踏实的。我们不仅要学会使用框架,更要去深入理解框架,至少要知道为什么我们就加了一个注解框架就能帮我们实现一大堆功能,只有这样才能让我们感到脚踏实地。下一篇,深入Shiro源码看看,可能需要酝酿一下想想笔记怎么写。


Shiro权限管理框架(二):Shiro结合Redis实现分布式环境下的Session共享的更多相关文章

  1. Shiro权限管理框架(一):Shiro的基本使用

    首发地址:https://www.guitu18.com/post/2019/07/26/43.html 核心概念 Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码 ...

  2. Shiro权限管理框架(三):Shiro中权限过滤器的初始化流程和实现原理

    本篇是Shiro系列第三篇,Shiro中的过滤器初始化流程和实现原理.Shiro基于URL的权限控制是通过Filter实现的,本篇从我们注入的ShiroFilterFactoryBean开始入手,翻看 ...

  3. Shiro权限管理框架(四):深入分析Shiro中的Session管理

    其实关于Shiro的一些学习笔记很早就该写了,因为懒癌和拖延症晚期一直没有落实,直到今天公司的一个项目碰到了在集群环境的单点登录频繁掉线的问题,为了解决这个问题,Shiro相关的文档和教程没少翻.最后 ...

  4. Shiro权限管理框架(五):自定义Filter实现及其问题排查记录

    明确需求 在使用Shiro的时候,鉴权失败一般都是返回一个错误页或者登录页给前端,特别是后台系统,这种模式用的特别多.但是现在的项目越来越多的趋向于使用前后端分离的方式开发,这时候就需要响应Json数 ...

  5. 转:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权

    原文地址:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法.shiro认证与shiro授权 以下是部分内容,具体见原文. shiro介绍 什么是shiro shiro是Apache ...

  6. shiro基础学习(二)—shiro认证

    一.shiro简介      shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证.权限授权.加密.会话管理等功能,组成了一个通用的安全认证框架. 以下 ...

  7. 用redis实现TOMCAT集群下的session共享

    上篇实现了 LINUX中NGINX反向代理下的TOMCAT集群(http://www.cnblogs.com/yuanjava/p/6850764.html) 这次我们在上篇的基础上实现session ...

  8. Redis+Tomcat+Nginx集群实现Session共享,Tomcat Session共享

    Redis+Tomcat+Nginx集群实现Session共享,Tomcat Session共享 ============================= 蕃薯耀 2017年11月27日 http: ...

  9. [原]Redis主从复制各种环境下测试

    Redis 主从复制各种环境下测试 测试环境: Linux ubuntu 3.11.0-12-generic 2GB Mem 1 core of Intel(R) Core(TM) i5-3470 C ...

随机推荐

  1. Electron构建一个文件浏览器应用(二)

    在前一篇文章我们已经学习到了使用Electron来构建我们的文件浏览器了基础东西了,我们之前已经完成了界面功能和显示文件或文件夹的功能了,想看之前文章,请点击这个链接  .现在我们需要在之前的基础上来 ...

  2. SkyWorking基础:6.2版本安装部署

    就在今天,SkyWorking发布了6.2版本. 概述 什么是SkyWorking SkyWalking是观察性分析平台和应用性能管理系统. 提供分布式追踪.服务网格遥测分析.度量聚合和可视化一体化解 ...

  3. Java---使用EWS读取exchange邮件

    第一步: 下载EWS API相关包: 从如下路径下载EWS API包:http://code.msdn.microsoft.com/Exchange-EWS-Java-API-12-1a5a1143 ...

  4. 【Netty整理03-NIO】Java 实现 NIO demo

    jdk提供的NIO使用: 概览:https://blog.csdn.net/the_fool_/article/details/83000648 博主抄写了网上的demo,略作修改与调整,原文链接: ...

  5. (1)Linux文件系统的目录组成

    记忆秘诀:BBDEH OPRLM TLSUV 宝贝的恩惠 欧派入联盟 偷了suv,19   目录 英文释义 简写 详解 1 /   根目录 整个文件系统的唯一根目录 2 /bin Binary 普通命 ...

  6. 如何使用JavaScript导入和导出Excel文件

    本文由葡萄城技术团队于原创并首发 转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. JavaScript是一个涵盖多种框架.直译式.可以轻松自定义客户端的脚本 ...

  7. 每日一问:谈谈 SharedPreferences 的 apply() 和 commit()

    SharedPreferences 应该是任何一名 Android 初学者都知道的存储类了,它轻量,适合用于保存软件配置等参数.以键值对的 XML 文件形式存储在本地,程序卸载后也会一并清除,不会残留 ...

  8. C++ 洛谷 P2458 [SDOI2006]保安站岗 from_树形DP

    P2458 [SDOI2006]保安站岗 没学树形DP的,看一下. 题目大意:一棵树有N个节点,现在需要将所有节点都看守住,如果我们选择了节点i,那么节点i本身,节点i的父亲和儿子都会被看守住. 每个 ...

  9. python初识(3)

    bool 字符串 for循环 bool 数字非零全都是True 字符串非空全都是True 字符串 索引 从0开始 0 切片选取 [x:y] 左闭右开区间 [x:y:z] 选取x到y之间 每隔z选取一次 ...

  10. 按行读取String类型

    BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content.getByt ...