当前位置: 首页 > news >正文

解决Springboot整合Shiro自定义SessionDAO+Redis管理会话,登录后不跳转首页

解决Springboot整合Shiro自定义SessionDAO+Redis管理会话,登录后不跳转首页

  • 问题发现
  • 问题解决

问题发现

Shiro框架中,SessionDAO的默认实现是MemorySessionDAO。它内部维护了一个ConcurrentMap来保存session数据,即将session数据缓存在内存中。

再使用Redis作为Session存储解决分布式系统中的Session共享问题。

依赖文件如下:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.7.18</version>
</dependency>
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>1.13.0</version>
</dependency>

示例代码如下:

@Controller
@RequestMapping(value = "/user")
public class UserController {@GetMapping("/index")public ModelAndView index() {Subject subject = SecurityUtils.getSubject();System.out.println("===============index==========");System.out.println(subject.getSession().getId());System.out.println(subject.isAuthenticated());if (subject.isAuthenticated() || subject.isRemembered()) {return new ModelAndView("redirect:main");}return new ModelAndView("login.html");}@PostMapping("/login")public ModelAndView login(HttpServletRequest request, @RequestParam("username") String username, @RequestParam("password") String password) {// 提前加密,解决自定义缓存匹配时错误UsernamePasswordToken token = new UsernamePasswordToken(username,//身份信息password);//凭证信息ModelAndView modelAndView = new ModelAndView();// 对用户信息进行身份认证Subject subject = SecurityUtils.getSubject();if (subject.isAuthenticated() && subject.isRemembered()) {modelAndView.setViewName("redirect:main");return modelAndView;}try {subject.login(token);// 判断savedRequest不为空时,获取上一次停留页面,进行跳转SavedRequest savedRequest = WebUtils.getSavedRequest(request);if (savedRequest != null) {String requestUrl = savedRequest.getRequestUrl();modelAndView.setViewName("redirect:"+ requestUrl);return modelAndView;}} catch (AuthenticationException e) {e.printStackTrace();modelAndView.addObject("responseMessage", "用户名或者密码错误");modelAndView.setViewName("redirect:index");return modelAndView;}System.out.println(subject.getSession().getId());System.out.println(subject.isAuthenticated());modelAndView.setViewName("redirect:main");return modelAndView;}@GetMapping("/main")public String main() {Subject subject = SecurityUtils.getSubject();System.out.println("===============main==========");System.out.println(subject.getSession().getId());System.out.println(subject.isAuthenticated());return "main.html";}
}

自定义SessionDAO,示例代码如下:

public class RedisSessionDao extends AbstractSessionDAO {private HashOperations<String, Object, Session> hashOperations;private static final String key = "shiro:";public RedisSessionDao(RedisTemplate<String, Object> redisTemplate) {hashOperations = redisTemplate.opsForHash();}@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = super.generateSessionId(session);this.assignSessionId(session, sessionId);this.storeSession(sessionId, session);return sessionId;}@Overrideprotected Session doReadSession(Serializable serializable) {return (Session) hashOperations.get(key, serializable.toString());}@Overridepublic void update(Session session) throws UnknownSessionException {this.storeSession(session.getId(), session);}@Overridepublic void delete(Session session) {if (session == null) {throw new NullPointerException("session argument cannot be null.");} else {Serializable id = session.getId();if (id != null) {hashOperations.delete(key, id.toString());}}}@Overridepublic Collection<Session> getActiveSessions() {return hashOperations.values(key);}protected void storeSession(Serializable id, Session session) {if (id == null) {throw new NullPointerException("id argument cannot be null.");} else {this.hashOperations.putIfAbsent(key, id.toString(), session);}}
}

Config配置文件示例代码如下:

@Configuration
public class ShiroConfig {/*** 核心安全过滤器对进入应用的请求进行拦截和过滤,从而实现认证、授权、会话管理等安全功能。*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 当未登录的用户尝试访问受保护的资源时,重定向到这个指定的登录页面。shiroFilterFactoryBean.setLoginUrl("/user/index");// 成功后跳转地址,但是测试时未生效shiroFilterFactoryBean.setSuccessUrl("/user/main");// 当用户访问没有权限的资源时,系统重定向到指定的URL地址。Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();filterChainDefinitionMap.put("/user/login", "anon");filterChainDefinitionMap.put("/**", "authc");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}/*** 创建Shiro Web应用的整体安全管理*/@Beanpublic DefaultWebSecurityManager securityManager() {DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();defaultWebSecurityManager.setRealm(realm());defaultWebSecurityManager.setSessionManager(defaultWebSessionManager()); // 注册会话管理// 可以添加其他配置,如缓存管理器、会话管理器等return defaultWebSecurityManager;}/*** 创建会话管理*/@Beanpublic DefaultWebSessionManager defaultWebSessionManager() {DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();defaultWebSessionManager.setGlobalSessionTimeout(10000);defaultWebSessionManager.setSessionDAO(sessionDAO());defaultWebSessionManager.setCacheManager(cacheManager());return defaultWebSessionManager;}@Beanpublic SessionDAO sessionDAO() {RedisSessionDao redisSessionDao = new RedisSessionDao(redisTemplate());return redisSessionDao;}/*** 指定密码加密算法类型*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("SHA-256"); // 设置哈希算法return hashedCredentialsMatcher;}/*** 注册Realm的对象,用于执行安全相关的操作,如用户认证、权限查询*/@Beanpublic Realm realm() {UserRealm userRealm = new UserRealm();userRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 为realm设置指定算法userRealm.setCachingEnabled(true); // 启动全局缓存userRealm.setAuthorizationCachingEnabled(true); // 启动授权缓存userRealm.setAuthenticationCachingEnabled(true); // 启动验证缓存userRealm.setCacheManager(cacheManager());return userRealm;}@Beanpublic CacheManager cacheManager() {RedisCacheManage redisCacheManage = new RedisCacheManage(redisTemplate());return redisCacheManage;}@Autowiredprivate RedisConnectionFactory redisConnectionFactory;// redis序列化配置@Beanpublic RedisTemplate<String, Object> redisTemplate() {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);ObjectMapper objectMapper = new ObjectMapper();//设置了 ObjectMapper 的可见性规则。通过该设置,所有字段(包括 private、protected 和 package-visible 等)都将被序列化和反序列化,无论它们的可见性如何。objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);//启用了默认的类型信息 NON_FINAL 参数表示只有非 final 类型的对象才包含类型信息,这可以帮助在反序列化时正确地将 JSON 字符串转换回对象。objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);jackson2JsonRedisSerializer.setObjectMapper(objectMapper);StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();redisTemplate.setHashKeySerializer(stringRedisSerializer);redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer());return redisTemplate;}
}

进入浏览器登陆成功后跳转首页,跳转过程中302,返回登录页面,如图所示:
在这里插入图片描述

问题解决

根据代码日志,可知道,跳转到其他页面时Session没有共享,如图所示:

在这里插入图片描述
最开始以为Redis中没有保存记录,其实已经保存了,如图所示:

在这里插入图片描述
参考网上诸多案例,似乎没什么区别,也不知道他们测过没有。

然后再Debug的时候,发现了另外一个类EnterpriseCacheSessionDAO,于是参考该类,我就把对应代码继承CachingSessionDAO,示例代码如下:

public class RedisSessionDao extends CachingSessionDAO {private HashOperations<String, Object, Session> hashOperations;protected Serializable doCreate(Session session) {Serializable sessionId = this.generateSessionId(session);this.assignSessionId(session, sessionId);return sessionId;}protected Session doReadSession(Serializable sessionId) {return null;}protected void doUpdate(Session session) {}protected void doDelete(Session session) {}
}

Config配置文件,示例代码如下:

    @Beanpublic DefaultWebSessionManager defaultWebSessionManager() {DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();defaultWebSessionManager.setGlobalSessionTimeout(10000);defaultWebSessionManager.setSessionDAO(sessionDAO());defaultWebSessionManager.setCacheManager(cacheManager());return defaultWebSessionManager;}@Beanpublic SessionDAO sessionDAO() {RedisSessionDao redisSessionDao = new RedisSessionDao();redisSessionDao.setCacheManager(cacheManager()); // 设置缓存管理器redisSessionDao.setActiveSessionsCacheName("shiro:session"); // 自定义redis存放的key名称return redisSessionDao;}

重启项目后运行,成功跳转,如图所示:

在这里插入图片描述
Redis中也有记录,如图所示:
在这里插入图片描述
至于继承AbstractSessionDAO为什么没有共享Session,大概率的原因是Redis没有被Shiro给管理导致的。

示例代码如下:

public class RedisSessionDao extends AbstractSessionDAO {private CacheManager cacheManager;private Cache<Serializable, Session> activeSessions;private static final String key = "shiro:";public RedisSessionDao() {}public void setCacheManager(CacheManager cacheManager) {this.cacheManager = cacheManager;this.activeSessions = cacheManager.getCache(key);}@Overrideprotected Serializable doCreate(Session session) {Serializable sessionId = super.generateSessionId(session);this.assignSessionId(session, sessionId);this.storeSession(sessionId, session);return sessionId;}@Overrideprotected Session doReadSession(Serializable serializable) {return (Session) activeSessions.get(serializable);}@Overridepublic void update(Session session) throws UnknownSessionException {this.storeSession(session.getId(), session);}@Overridepublic void delete(Session session) {if (session == null) {throw new NullPointerException("session argument cannot be null.");} else {Serializable id = session.getId();if (id != null) {activeSessions.remove(id);}}}@Overridepublic Collection<Session> getActiveSessions() {return activeSessions.values();}protected void storeSession(Serializable id, Session session) {if (id == null) {throw new NullPointerException("id argument cannot be null.");} else {activeSessions.put(id, session);}}
}

配置文件,示例代码如下:

    /*** 创建会话管理*/@Beanpublic DefaultWebSessionManager defaultWebSessionManager() {DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();defaultWebSessionManager.setGlobalSessionTimeout(10000);defaultWebSessionManager.setSessionDAO(sessionDAO());defaultWebSessionManager.setCacheManager(cacheManager());return defaultWebSessionManager;}@Beanpublic SessionDAO sessionDAO() {RedisSessionDao redisSessionDao = new RedisSessionDao();redisSessionDao.setCacheManager(cacheManager()); // 设置缓存管理器return redisSessionDao;}

经过测试也是可以成功跳转,会话共享。

在这里插入图片描述

相关文章:

解决Springboot整合Shiro自定义SessionDAO+Redis管理会话,登录后不跳转首页

解决Springboot整合Shiro自定义SessionDAORedis管理会话&#xff0c;登录后不跳转首页 问题发现问题解决 问题发现 在Shiro框架中&#xff0c;SessionDAO的默认实现是MemorySessionDAO。它内部维护了一个ConcurrentMap来保存session数据&#xff0c;即将session数据缓存在内存…...

Day56 图论part06

108.冗余连接 并查集应用类题目,关键是如何把题意转化成并查集问题 代码随想录 import java.util.Scanner;public class Main{public static void main (String[] args) {Scanner scanner = new Scanner(System.in);int n = scanner.nextInt();DisJoint disjoint = new DisJo…...

[python SQLAlchemy数据库操作入门]-04.连接数据库:增删改查

哈喽,大家好,我是木头左! 通过使用 SQLAlchemy,开发者可以在 Python 中以更直观的方式操作数据库,而无需编写大量的 SQL 代码。 创建数据库引擎 在 SQLAlchemy 中,数据库引擎是用于与数据库交互的核心组件。它负责管理数据库连接,并执行 SQL 语句。 示例:连接到 SQLi…...

黑马点评——基于Redis

目录 1.短信登录 1.1基于Session登录&#xff08;已被Redis代替&#xff09; 1.2cookie和session 2.添加Redis缓存 2.1根据id查询商户信息 2.2缓存穿透 2.3缓存雪崩 《黑马点评》Redis高并发项目实战笔记【完结】P1~P72_黑马点评笔记-CSDN博客 1.短信登录 1.1基于Sess…...

RocketMQ的集群架构是怎样的?

大家好&#xff0c;我是锋哥。今天分享关于【RocketMQ的集群架构是怎样的?】面试题。希望对大家有帮助&#xff1b; RocketMQ的集群架构是怎样的? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 RocketMQ 是阿里巴巴开源的分布式消息中间件&#xff0c;广泛用于处…...

VS2022+QT6.7 窗口置灰(遮罩)

本文章使用QWidget来使窗口置灰&#xff0c;使用按钮控制置灰功能的开启和关闭&#xff0c;同时被置灰的控件自动禁用交互功能。 connect(ui.pushButton_open, &QPushButton::clicked, this, []() { //创建无边框窗口,大小是父的大小QWidget* parentWidget new QWidget…...

如何通过HTTP API插入或更新Doc

本文介绍如何通过HTTP API向Collection中插入或更新Doc。 说明 若调用本接口时Doc Id已存在&#xff0c;则等同于更新Doc&#xff1b;Doc Id不存在&#xff0c;则等同于插入Doc。 若调用本接口时不指定Doc Id&#xff0c;则等同于插入Doc&#xff0c;DashVector会自动生成Doc …...

C++ STM32 F4xx USART LL库 DMA + IDLE ISR 驱动裸机 +FreeRTOS 任务通知

写的一般&#xff0c;大佬可以帮我看看 头文件 /********************************************************************************* file : usart_driver.hpp* brief : usart_driver program head**************************************************…...

RK3588在Android13/14如何查看GPU,NPU,DDR,RGA数据

由于Android13上selinux的权限管控加强&#xff0c;原来android12的方法已经无法获取到性能相关数据了&#xff0c;故单独介绍Android13上的性能数据获取 首先需要保障能过获取到root权限&#xff0c;adb root能够生效&#xff0c;adb shell进入shell命令行 mount -t debugfs…...

sentinel学习笔记6-限流降级(上)

本文属于sentinel学习笔记系列。网上看到吴就业老师的专栏&#xff0c;写的好值得推荐&#xff0c;我整理的有所删减&#xff0c;推荐看原文。 https://blog.csdn.net/baidu_28523317/category_10400605.html sentinel 实现限流降级、熔断降级、黑白名单限流降级、系统自适应…...

【Rust自学】6.4. 简单的控制流-if let

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 6.4.1. 什么是if let if let语法允许将if和let组合成一种不太冗长的方式来处理与一种模式匹配的值&#xff0c;同时忽略其余模式。 可以…...

【HarmonyOS】鸿蒙将资源文件夹Resource-RawFile下的文件存放到沙箱目录下

【HarmonyOS】鸿蒙将资源文件夹Resource-RawFile下的文件存放到沙箱目录下 一、问题背景 应用开发中&#xff0c;我们经常会遇到一些文件原先是放在资源文件夹 rawfile下&#xff0c;但是逻辑处理时&#xff0c;需要转移到本地沙箱才能操作。这种情况下&#xff0c;就需要将将…...

Vue项目中env文件的作用和配置

在实际项目的开发中&#xff0c;我们一般会经历项目的开发阶段、测试阶段和最终上线阶段&#xff0c;每一个阶段对于项目代码的要求可能都不尽相同&#xff0c;那么我们如何能够游刃有余的在不同阶段下使我们的项目呈现不同的效果&#xff0c;使用不同的功能呢&#xff1f;这里…...

在 Vue3 项目中实现计时器组件的使用(Vite+Vue3+Node+npm+Element-plus,附测试代码)

一、概述 记录时间 [2024-12-26] 本文讲述如何在 Vue3 项目中使用计时器组件。具体包括开发环境的配置&#xff0c;ViteVue 项目的创建&#xff0c;Element Plus 插件的使用&#xff0c;以及计时器组件的创建和使用。 想要直接实现计时器组件&#xff0c;查看文章的第四部分。…...

机器人C++开源库The Robotics Library (RL)使用手册(三)

进入VS工程,我们先看看这些功能函数及其依赖库的分布关系: rl命名空间下,主要有八大模块。 搞定VS后将逐个拆解。 1、编译运行 根据报错提示,配置相应错误的库(根据每个人安装位置不同而不同,我的路径如下:) 编译所有,Release版本耗时大约10分钟。 以rlPlan运动…...

Photoshop启动错误:找不到MSVCP140.dll的多步骤解决指南

在使用Adobe Photoshop&#xff08;简称PS&#xff09;进行创意设计或图像编辑时&#xff0c;有时会遇到软件启动报错的情况&#xff0c;其中“找不到MSVCP140.dll&#xff0c;无法继续执行代码”是一个常见的错误提示。这个错误通常意味着你的系统缺少了Microsoft Visual C Re…...

mac中idea菜单工具栏没有git图标了

1.右击菜单工具栏 2.选中VCS&#xff0c;点击添加 3.搜索你要的工具&#xff0c;选中点击确定就添加了 4.回到上面一个界面&#xff0c;选中你要放到工具栏的工具&#xff0c;点击应用就好了 5.修改图标&#xff0c;快捷键或者右击选中编辑图标 6.选择你要的图标就好了...

学习threejs,PerspectiveCamera透视相机和OrthographicCamera正交相机对比

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.PerspectiveCamera透…...

C#数学相关开发性能优化方法

本文Github地址&#xff1a;CSharp-MathOptimization.md 华为公司的C语言编程规范在开头就强调了&#xff1a; 一般情况下&#xff0c;代码的可阅读性高于性能&#xff0c;只有确定性能是瓶颈时&#xff0c;才应该主动优化。 本文讲述的方法没有经过大项目和大公司的检验&…...

【前沿 热点 顶会】AAAI 2025中与目标检测有关的论文

CP-DETR: Concept Prompt Guide DETR Toward Stronger Universal Object Detection&#xff08;AAAI 2025&#xff09; 最近关于通用物体检测的研究旨在将语言引入最先进的闭集检测器&#xff0c;然后通过构建大规模&#xff08;文本区域&#xff09;数据集进行训练&#xff0…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

stm32G473的flash模式是单bank还是双bank?

今天突然有人stm32G473的flash模式是单bank还是双bank&#xff1f;由于时间太久&#xff0c;我真忘记了。搜搜发现&#xff0c;还真有人和我一样。见下面的链接&#xff1a;https://shequ.stmicroelectronics.cn/forum.php?modviewthread&tid644563 根据STM32G4系列参考手…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

算法岗面试经验分享-大模型篇

文章目录 A 基础语言模型A.1 TransformerA.2 Bert B 大语言模型结构B.1 GPTB.2 LLamaB.3 ChatGLMB.4 Qwen C 大语言模型微调C.1 Fine-tuningC.2 Adapter-tuningC.3 Prefix-tuningC.4 P-tuningC.5 LoRA A 基础语言模型 A.1 Transformer &#xff08;1&#xff09;资源 论文&a…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Oracle11g安装包

Oracle 11g安装包 适用于windows系统&#xff0c;64位 下载路径 oracle 11g 安装包...

离线语音识别方案分析

随着人工智能技术的不断发展&#xff0c;语音识别技术也得到了广泛的应用&#xff0c;从智能家居到车载系统&#xff0c;语音识别正在改变我们与设备的交互方式。尤其是离线语音识别&#xff0c;由于其在没有网络连接的情况下仍然能提供稳定、准确的语音处理能力&#xff0c;广…...

Ubuntu系统多网卡多相机IP设置方法

目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机&#xff0c;交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息&#xff0c;系统版本&#xff1a;Ubuntu22.04.5 LTS&#xff1b;内核版本…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...