解决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;}

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

在这里插入图片描述

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

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

相关文章

java历史版本信息

Java是由Sun Microsystems&#xff08;后被Oracle公司收购&#xff09;于1995年推出的面向对象程序设计语言和Java平台的总称。到目前为止&#xff0c;Java已经发布了多个版本&#xff0c;以下是Java的主要版本及其发布时间。 一般来说&#xff0c;LTS版本&#xff08;长期支持…

windows nmake 安装openssl

windows nmake 编译和安装 openssl 本文提供了在Windows环境下安装OpenSSL的详细步骤&#xff0c;包括下载Perl、NASM和VisualStudio&#xff0c;配置环境变量&#xff0c;使用PerlConfigure设置平台&#xff0c;通过nmake进行编译、测试和安装。整个过程涉及32位和64位版本的选…

Spring Boot应用开发实战:从入门到精通

一、Spring Boot 简介 1.1 什么是 Spring Boot&#xff1f; Spring Boot 是一个开源框架&#xff0c;旨在简化新 Spring 应用的初始搭建以及开发过程。它构建在 Spring 框架之上&#xff0c;利用了 Spring 的核心特性&#xff0c;如依赖注入&#xff08;Dependency Injection&…

一、Hadoop概述

文章目录 一、Hadoop是什么二、Hadoop发展历史三、Hadoop三大发行版本1. Apache Hadoop2. Cloudera Hadoop3. Hortonworks Hadoop四、Hadoop优势1. 高可靠性2. 高扩展性3. 高效性4. 高容错性五、Hadoop 组成1. Hadoop1.x、2.x、3.x区别2. HDFS 架构概述3. YARN 架构概述4. MapR…

python版本的Selenium的下载及chrome环境搭建和简单使用

针对Python版本的Selenium下载及Chrome环境搭建和使用&#xff0c;以下将详细阐述具体步骤&#xff1a; 一、Python版本的Selenium下载 安装Python环境&#xff1a; 确保系统上已经安装了Python 3.8及以上版本。可以从[Python官方网站]下载并安装最新版本的Python&#xff0c;…

vue---- H5页面 pdf,docx,excel文件预览下载功能

vue---- H5页面 pdf&#xff0c;docx&#xff0c;excel文件预览&&下载功能 pdf&#xff0c;docx&#xff0c;excel文件预览&&下载适用于vue2和vue3&#xff0c;示例为vue3 1.npm下载这些文件的插件&#xff08;选择自己需要预览的进行下载&#xff09; 安装pd…

vue3和springboot使用websocket通信

前端端口&#xff1a;9090 后端端口&#xff1a;8080 vue3 引入依赖&#xff1a; npm install sockjs-client stomp/stompjs vue页面 <template><div><h1>WebSocket 示例</h1><button click"sendMessage">发送消息</button>…

【时时三省】(C语言基础)动态内存函数malloc

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 malloc 开辟内存块 使用格式 void *malloc&#xff08;size_t sie&#xff09;&#xff1b; 示例 10*sizeof(int&#xff09;就是开辟空间的大小 如果p是void指针的话 p不能解引用 m…

c#多线程之生产者-消费者模型

在 C# 中实现 生产者-消费者模式&#xff0c;通常需要多个线程来处理数据的生产和消费。我们可以使用 Queue<T> 来作为存储数据的队列&#xff0c;并使用 Thread、Mutex 或 Monitor 来确保线程安全。BlockingCollection<T> 是 C# 提供的一个线程安全的集合&#xf…

选煤厂可视化技术助力智能化运营

通过图扑 HT 可视化搭建智慧选煤厂管理平台&#xff0c;优化了选煤生产流程&#xff0c;提高了资源利用率和安全性&#xff0c;助力企业实现智能化运营和可持续发展目标。

【论文笔记】Visual Alignment Pre-training for Sign Language Translation

&#x1f34e;个人主页&#xff1a;小嗷犬的个人主页 &#x1f34a;个人网站&#xff1a;小嗷犬的技术小站 &#x1f96d;个人信条&#xff1a;为天地立心&#xff0c;为生民立命&#xff0c;为往圣继绝学&#xff0c;为万世开太平。 基本信息 标题: Visual Alignment Pre-tra…

深入浅出 MyBatis | CRUD 操作、配置解析

3、CRUD 3.1 namespace namespace 中的包名要和 Dao/Mapper 接口的包名一致&#xff01; 比如将 UserDao 改名为 UserMapper 运行发现抱错&#xff0c;这是因为 UserMapper.xml 中没有同步更改 namespace 成功运行 给出 UserMapper 中的所有接口&#xff0c;接下来一一对…

前端:改变鼠标点击物体的颜色

需求&#xff1a; 需要改变图片中某一物体的颜色&#xff0c;该物体是纯色&#xff1b; 鼠标点击哪个物体&#xff0c;哪个物体的颜色变为指定的颜色&#xff0c;利用canvas实现。 演示案例 代码Demo <!DOCTYPE html> <html lang"en"><head>&l…

递归算法常见问题(Java)

问题&#xff1a;斐波那契数列,第1项和第2项都为1&#xff0c;后面每一项都为相邻的前俩项的和,求第n个数 解法&#xff1a;每一个数都为前俩个数之和&#xff0c;第1项和第2项都为1&#xff0c;所以写 方法f1(n)即为求第n个数&#xff0c;那么f1(n-1)为求第n-1个数&#xff0…

git自动压缩提交的脚本

可以将当前未提交的代码自动执行 git addgit commitgit squash Git 命令安装指南 1. 创建脚本目录 如果目录不存在&#xff0c;创建它&#xff1a; mkdir -p ~/.local/bin2. 创建脚本文件 vim ~/.local/bin/git-squash将完整的脚本代码复制到此文件中。 3. 设置脚本权限…

C项目 天天酷跑(下篇)

上篇再博客里面有&#xff0c;接下来我们实现我们剩下要实现的功能 文章目录 碰撞检测 血条的实现 积分计数器 前言 我们现在要继续优化我们的程序才可以使这个程序更加的全面 碰撞的检测 定义全局变量 实现全局变量 void checkHit() {for (int i 0; i < OBSTACLE_C…

论文解读——掌纹生成网络 RPG-Palm升级版PCE-Palm

该文章是2023年论文RPG-Palm的升级版 论文&#xff1a;PCE-Palm: Palm Crease Energy Based Two-Stage Realistic Pseudo-Palmprint Generation 作者&#xff1a;Jin, Jianlong and Shen, Lei and Zhang, Ruixin and Zhao, Chenglong and Jin, Ge and Zhang, Jingyun and Ding,…

代码随想录算法【Day2】

Day2 1.掌握滑动窗口法 2.模拟题&#xff0c;坚持循环不变量原则 209 长度最小的子数组 暴力法&#xff1a; class Solution { public:int minSubArrayLen(int target, vector<int>& nums) {//暴力法int i, j; //i代表起始点&#xff0c;j代表终止点int sum; //…

android——屏幕适配

一、屏幕适配的重要性 在 Android 开发中&#xff0c;屏幕适配是非常关键的。因为 Android 设备具有各种各样的屏幕尺寸、分辨率和像素密度。如果没有进行良好的屏幕适配&#xff0c;应用可能会出现显示不完整、元素拉伸或压缩变形、字体大小不合适等问题&#xff0c;极大地影响…

oscp学习之路,Kioptix Level2靶场通关教程

oscp学习之路&#xff0c;Kioptix Level2靶场通关教程 靶场下载&#xff1a;Kioptrix Level 2.zip 链接: https://pan.baidu.com/s/1gxVRhrzLW1oI_MhcfWPn0w?pwd1111 提取码: 1111 搭建好靶场之后输入ip a看一下攻击机的IP。 确定好本机IP后&#xff0c;使用nmap扫描网段&…