4_springboot_shiro_jwt_多端认证鉴权_Redis存储会话

1. 什么是会话

所谓的会话,就是用户与应用程序在某段时间内的一系列交互,在这段时间内应用能识别当前访问的用户是谁,而且多次交互中可以共享数据。我们把一段时间内的多次交互叫做一次会话。

即用户登录认证后,多次与应用进行交互,直到调用退出登录,这就是一次会话。如果登录后一直没有调用退出登录,那么一段时间后,这个会话会自动结束。

在Shiro中,将会话定义为一个接口:

package org.apache.shiro.session;import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
public interface Session {// 获取sessionIDSerializable getId();// 会话开始的时间Date getStartTimestamp();// 最后一次访问时间Date getLastAccessTime();// 获取会话过期时间(毫秒)long getTimeout() throws InvalidSessionException;// 设置会话过期时间void setTimeout(long maxIdleTimeInMillis) throws InvalidSessionException;String getHost();void touch() throws InvalidSessionException;// 可以在一次会话中共享数据,数据以 key-value 的形式保存,这里返回所有的keyCollection<Object> getAttributeKeys() throws InvalidSessionException;//  可以在一次会话中共享数据,通过key 获取相关的数据Object getAttribute(Object key) throws InvalidSessionException;//  可以在一次会话中共享数据,通过key 保存相关的数据void setAttribute(Object key, Object value) throws InvalidSessionException;//  可以在一次会话中共享数据,通过key 删除相关数据Object removeAttribute(Object key) throws InvalidSessionException;
}

Shiro 会话的概念与 JavaEE Servlet 容器中的会话概念是一致的。但是Shiro会话比 JavaEE 容器会话概念要大一些。因为Shiro的会话管理不依赖于底层容器(如 tomcat web 容器), 不管是 JavaSE 还是 JavaEE环境,它都可以使用。如果使用了Shiro,那么可以直接使用Shiro的会话管理来替代Web容器的会话管理。

2. Shiro会话管理相关接口

Shiro中,实现了 org.apache.shiro.session.Session接口的都是会话对象, 在这个对象的管理上也有一套接口来支撑.

  • org.apache.shiro.session.mgt.eis.SessionDAO 会话怎么创建,如何保存,如何更新。即会话的 增,删,改,查

    public interface SessionDAO {// 保存session对象到 数据库,或者持久化的缓存,或者文件系统中。这依赖于具体的实现。保存完毕之后返回的是 ID,即sessionIdSerializable create(Session session);// 根据sessionId 获取整个Session对象Session readSession(Serializable sessionId) throws UnknownSessionException;// 更新Sessionvoid update(Session session) throws UnknownSessionException;// 删除Sessionvoid delete(Session session);// 获取所有活动的sessionCollection<Session> getActiveSessions();
    
  • org.apache.shiro.session.mgt.eis.SessionManager 如何开始一个会话,根据key获取session对象

    public interface SessionManager {//此方法主要用于框架开发,因为实现通常会将参数传递给底层SessionFactory,后者可以使用上下文以特定方式构造内部会话实例。只需将SessionFactory注入到SessionManager实例中,就可以实现可插入的会话创建逻辑。Session start(SessionContext context);// 使用key 检索sessionSession getSession(SessionKey key) throws SessionException;
    }
    
  • org.apache.shiro.session.mgt.SessionKey session的key标识,不一定是个字符串,实现了Serializable都可以作为session的key

    public interface SessionKey {Serializable getSessionId();
    }
    
  • org.apache.shiro.session.SessionListener 监听器,通过它可以监听到session什么时候开始,什么时候借宿,什么时候过期

    public interface SessionListener {void onStart(Session session);void onStop(Session session);void onExpiration(Session session);
    }
    

3. 应用中如何使用session

开发的时候,我们往往需要在一次会话的多次交互中共享数据通常是这样来实现的:

Subject curSubject = SecurityUtils.getSubject();
// 取得当前session
Session session = currentUser.getSession();
// 存放共享数据
session.setAttribute("someKey", someValue);
// 获取共享数据
Object someValue= session.getAttribute("someKey")
// 删除数据
session.removeAttribute("someKey")

4. SessionDAO

Session中的数据是保存在服务端的,至于到底保存到哪里(数据库中\内存中\磁盘文件中) 由具体的 SessionDAO来实现。本小节实现将Session中的数据保存到Redis中。

4.1 默认SessionDAO

还是先看看框架默认使用的是哪个具体的SessionDAO, 查看自动配置类ShiroWebAutoConfiguration:

@Configuration
@AutoConfigureBefore(ShiroAutoConfiguration.class)
@AutoConfigureAfter(ShiroWebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {...// 创建SessionDAO Bean@Bean@ConditionalOnMissingBean@Overrideprotected SessionDAO sessionDAO() {return super.sessionDAO();}...}public class AbstractShiroConfiguration {...protected SessionDAO sessionDAO() {// 默认使用的是 MemorySessionDAOreturn new MemorySessionDAO();}...
}

默认的SessionDAO是 MemorySessionDAO,这在分布式集群环境下会造成数据的不一致,所以下面自己来扩展,将Session数据保存到Redis中。
在这里插入图片描述

AbstractSessionDAO还有个子类叫做 CachingSessionDAO,所以只需要我们自己定义一个类,去继承 CachingSessionDAO 即可。

4.2 自定义ShiroRedisSessionDAO

这个ShiroRedisSessionDAO 将所有的session数据都存储到Redis 的Hash结构中,Hash结构的Redis Key是固定的。 Hash结构的key是sessionID,value是整个Session对象。

package com.qinyeit.shirojwt.demos.shiro.cache;
...
//  将Session数据保存到Redis中, Redis中存储的数据是Hash结构
@Slf4j
public class ShiroRedisSessionDAO extends EnterpriseCacheSessionDAO {//redis中session名称前缀private String redisKey = "shiro:session";private RedisTemplate<Object, Object> redisTemplate;public ShiroRedisSessionDAO(RedisTemplate<Object, Object> redisTemplate, String redisKey) {this.redisTemplate = redisTemplate;this.redisKey = redisKey;}// 创建session,保存到数据库@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = super.doCreate(session);log.debug("doCreate:" + session.getId());redisTemplate.opsForHash().put(redisKey, session.getId(), session);return sessionId;}// 获取session@Overrideprotected Session doReadSession(Serializable sessionId) {log.debug("doReadSession:" + sessionId);// 先从缓存中获取session,如果没有再去数据库中获取Session session = super.doReadSession(sessionId);if (session == null) {session = (Session) redisTemplate.opsForHash().get(redisKey, sessionId);}return session;}// 更新session@Overrideprotected void doUpdate(Session session) {super.doUpdate(session);log.debug("doUpdate:" + session.getId());HashOperations<Object, Object, Object> hashOp = redisTemplate.opsForHash();if (!hashOp.hasKey(redisKey, session.getId())) {hashOp.put(redisKey, session.getId(), session);}}//删除session@Overrideprotected void doDelete(Session session) {log.debug("doDelete:" + session.getId());super.doDelete(session);HashOperations<Object, Object, Object> hashOp = redisTemplate.opsForHash();hashOp.delete(redisKey, session.getId());}//获取当前活动的session@Overridepublic Collection<Session> getActiveSessions() {return super.getActiveSessions();}
}

4.3 配置ShiroRedisSessionDAO

package com.qinyeit.shirojwt.demos.configuration;
...
@Configuration
@Slf4j
public class ShiroConfiguration {...// 配置SessionDAO@Beanpublic SessionDAO shiroRedisSessionDAO(RedisTemplate redisTemplate) {ShiroRedisSessionDAO sessionDAO = new ShiroRedisSessionDAO(redisTemplate, "shiro:session");// 活跃session缓存的名字sessionDAO.setActiveSessionsCacheName("shiro:active:session");sessionDAO.setCacheManager(cacheManager);return sessionDAO;}...
}   

5. 执行登录测试

执行正确的登录,看看redis中都保存了些什么?
在这里插入图片描述
很奇怪,明明SessionDAO,却并没有将会话保存到Redis中。

6. 没有调用SessionDAO的原因

在IDEA中,打开SessionDAO 这个接口,选中其中的 create方法,然后 ctrl+alt+F7 (windows环境) ,或者按住 ctrl+鼠标点击方法,可以看到这个方法都在哪些地方调用
在这里插入图片描述

我们发现这个方法是在DefaultSessionManager中调用的。找到这个DefaultSessionManager,发现它还有个子类DefaultWebSessionManager ,没有调用dao,有没有可能是 默认的SessionManager既不是 DefaultSessionManager 也不是 DefaultWebSessionManager

6.1 默认SessionManager

打开自动配置类,发现了如下代码:

@Configuration
@AutoConfigureBefore(ShiroAutoConfiguration.class)
@AutoConfigureAfter(ShiroWebMvcAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@ConditionalOnProperty(name = "shiro.web.enabled", matchIfMissing = true)
public class ShiroWebAutoConfiguration extends AbstractShiroWebConfiguration {...@Bean@ConditionalOnMissingBean@Overrideprotected SessionManager sessionManager() {// 调用了父类 AbstractShiroWebConfiguration 中的 sessionManager()方法return super.sessionManager();}...
}// 父类 AbstractShiroWebConfiguration
public class AbstractShiroWebConfiguration extends AbstractShiroConfiguration {...// 我们没有在外部做任何配置,所以它是false@Value("#{ @environment['shiro.userNativeSessionManager'] ?: false }")protected boolean useNativeSessionManager;...@Overrideprotected SessionManager sessionManager() {// 默认情况下useNativeSessionManager 为falseif (useNativeSessionManager) {return nativeSessionManager();}// 这就是默认使用的 SessionManagerreturn new ServletContainerSessionManager();}...protected SessionManager nativeSessionManager() {DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();webSessionManager.setSessionIdCookieEnabled(sessionIdCookieEnabled);webSessionManager.setSessionIdUrlRewritingEnabled(sessionIdUrlRewritingEnabled);webSessionManager.setSessionIdCookie(sessionCookieTemplate());webSessionManager.setSessionFactory(sessionFactory());webSessionManager.setSessionDAO(sessionDAO());webSessionManager.setDeleteInvalidSessions(sessionManagerDeleteInvalidSessions);return webSessionManager;}
}

可以看到,这个默认的SessionManager的实现类为:org.apache.shiro.web.session.mgt.ServletContainerSessionManager ,查看这个类,我们发现它根本就没有使用 SessionDAO。

默认启动的SessionManger其实是 ServletContainerSessionManager, 这个类的文档注释上写得很清楚,它不管理会话,因为Servlet容器提供了实际的管理支持。

所以配置的SessionDAO没有被调用是因为默认启动的是 ServletContainerSessionManager , Session的管理实际上由Servlet容器来完成,自然就不会调用SessionDAO来完成Session对象的增,删,改

6.2 替换默认SessionManager

看上面的第 22行和 28 行代码,只需要将shiro.userNativeSessionManager 配置为 true ,就可以启用。 当然也可以在配置文件中直接配置。这里就直接拷贝nativeSessionManager 的代码到配置文件中:

package com.qinyeit.shirojwt.demos.configuration;
...
@Configuration
@Slf4j
public class ShiroConfiguration {...// 配置SessionDAO@Beanpublic SessionDAO shiroRedisSessionDAO(RedisTemplate redisTemplate) {return new ShiroRedisSessionDAO(redisTemplate, "shiro:session");}// sessionManager配置@Beanpublic SessionManager sessionManager(SessionFactory sessionFactory,@Qualifier("sessionCookieTemplate") Cookie cookieTemplate,SessionDAO sessionDAO) {DefaultWebSessionManager webSessionManager = new DefaultWebSessionManager();// 开启Cookie,即由Cookie来传递 sessionID保持会话webSessionManager.setSessionIdCookieEnabled(true);// 开启URL重写,即可以从URL中获取sessionID来保持会话webSessionManager.setSessionIdUrlRewritingEnabled(true);// 自动配置中已经配置了cookieTemplate 直接注入进来,具体看 ShiroWebAutoConfiguration 类中bean的定义webSessionManager.setSessionIdCookie(cookieTemplate);// 自动配置中已经配置了sessionFactory 直接注入进来webSessionManager.setSessionFactory(sessionFactory);// 使用自定义的ShiroRedisSessionDAOwebSessionManager.setSessionDAO(sessionDAO);// 清理无效的sessionwebSessionManager.setDeleteInvalidSessions(true);// 开启session定时检查webSessionManager.setSessionValidationSchedulerEnabled(true);webSessionManager.setSessionValidationScheduler(new ExecutorServiceSessionValidationScheduler());return webSessionManager;}
...
}

现在清空redis后,再执行登录操作,然后查看redis中的key:
在这里插入图片描述

发现有三个缓存,这三个缓存都是 Redis Hash 数据类型

  • session缓存
  • 活动session缓存
  • 认证信息缓存

7. Session失效

在IDEA 中,打开SessionDAO源码,选中delete方法后,跟踪这个方法是如何被调用的。最终会跟踪到 subject.logout() 方法 所以退出登录的时候会到dao中将session删除掉.

sessionManager打开了session定时检查,会定时检查失效的session,然后进行清除

8. 总结

  1. 没有做任何配置的情况下,使用的是 ServletContainerSessionManager ,本质是由Servlet容器来管理会话
  2. 分布式集群环境下,使用 Redis来保存会话信息,则必须替换掉默认的ServletContainerSessionManager, 使用 DefaultWebSessionManager ,可以自己写配置Bean,也可以在 application.properties 中配置选项:shiro.userNativeSessionManager =true
  3. 用Redis来保存会话,需要自己实现 SessionDAO 接口,此时可以直接继承Shiro已经实现的子类 EnterpriseCacheSessionDAO

代码仓库 https://github.com/kaiwill/shiro-jwt , 本节代码在 4_springboot_shiro_jwt_多端认证鉴权_Redis存储会话 分支上.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/539647.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch从零开始实战21

Pytorch从零开始实战——Pix2Pix理论与实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——Pix2Pix理论与实战内容介绍数据集加载模型实现开始训练总结 内容介绍 Pix2Pix是一种用于用于图像翻译的通用框架&#xff0c;即图像到图像的转换。…

PostgreSQL开发与实战(6.3)体系结构3

作者&#xff1a;太阳 四、物理结构 4.1 软件安装目录 bin //二进制可执行文件 include //头文件目录 lib //动态库文件 share //文档以及配置模版文件4.2 数据目录 4.2.1 参数文件 pg_hba.conf //认证配置文件 p…

目标检测——YOLOv3算法解读

论文&#xff1a;YOLOv3&#xff1a;An Incremental Improvement 作者&#xff1a;Joseph Redmon, Ali Farhadi 链接&#xff1a;https://arxiv.org/abs/1804.02767 代码&#xff1a;http://pjreddie.com/yolo/ YOLO系列其他文章&#xff1a; YOLOv1通俗易懂版解读SSD算法解读…

ArcGIS全系列实战视频教程——9个单一课程组合+系列直播回放

《ArcGIS全系列实战视频教程》是由9个单一课程组合合成。组成一条ArcGIS入门实战各项专题深入应用学习全链条&#xff0c;让你学有方向、学有目的&#xff0c;系统全面掌握ArcGIS。 ArcGIS全系列实战视频教程——9个单一课程组合https://edu.csdn.net/combo/detail/2569 《Ar…

vue 基于elementUI/antd-vue, h函数实现message中嵌套链接跳转到指定路由 (h函数点击事件的写法)

效果如图&#xff1a; 点击message 组件中的 工单管理&#xff0c; 跳转到工单管理页面。 以下是基于vue3 antd-vue 代码如下&#xff1a; import { message } from ant-design-vue; import { h, reactive, ref, watch } from vue; import { useRouter } from vue-router; c…

jenkins+maven+gitlab自动化构建打包、部署

Jenkins自动化部署实现原理 环境准备 1、jenkins已经安装好 docker安装jenkins 2、gitlab已经安装好 docker安装gitlab 一、Jenkins系统配置 1.Global Tool Configuration 任务构建所用到的编译环境等配置&#xff0c;配置参考&#xff1a; jdk配置&#xff08;jenkins自带…

数据资产在制造行业的应用:释放潜在价值,驱动产业升级

随着信息技术的飞速发展&#xff0c;数据已成为各行各业的重要资产。在制造行业中&#xff0c;数据资产的应用更是日益广泛&#xff0c;为企业的生产、管理、决策等各个环节带来了前所未有的变革。本文将深入探讨数据资产在制造行业的应用&#xff0c;以及如何通过数据驱动实现…

递归类C++

1、汉诺塔 面试题 08.06. 汉诺塔问题 - 力扣(LeetCode) Why?为什么这个汉诺塔问题可以用递归来解决? 如何来解决汉诺塔问题? 如果N == 1时,A[0] B C,直接将A上的盘子转移到C上面。 如果N == 2时,A[1,0] B C,先将上面所有的盘子放到B,然后将A上最大的那个放到C,再把…

深度学习_20_卷积中的填充与步幅

如果图片本身比较小&#xff0c;卷积之后输出也会很小&#xff0c;那么可以在图片与卷积核相乘之前先填充一下&#xff0c;让输出为预期大小 一般填充后输入&#xff0c;输出相同 当图片比较大的时候&#xff0c;如果利用卷积核去得到我们想要的大小的话&#xff0c;得用到多层…

爬虫3_爬取翻页URL不变的网站

之前实现了对大学排数据爬取&#xff1a;爬虫2_2019年549所中国大学排名. 近期复现代码&#xff0c;发现原网站升级&#xff0c;在翻页时&#xff0c;发现URL不改变&#xff0c;修改代码&#xff0c;使用网页自动化工具selenium实现对该类网站数据获取。 #-*- coding: UTF-8 -…

【数据结构和算法初阶(C语言)】队列实操(概念实现+oj题目栈和队列的双向实现,超级经典!!!)

1. 队列的概念及结构 队列&#xff1a;只允许在一端进行插入数据操作&#xff0c;在另一端进行删除数据操作的特殊线性表&#xff0c; 队列具有先进先出 FIFO(First In First Out) 入队列&#xff1a;进行插入操作的一端称为队尾 出队列&#xff1a;进行删除操作的一端称为…

BUGKU-WEB shell

题目描述 题目截图如下&#xff1a; 描述&#xff1a; $poc "a#s#s#e#r#t";$poc_1 explode("#", $poc);$poc_2 $poc_1[0].$poc_1[1].$poc_1[2].$poc_1[3].$poc_1[4].$poc_1[5];$poc_2($_GET[s])进入场景看看&#xff1a;是一个空白的界面 解题思路 …