解決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.pswp.cn/web/64109.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64109.shtml
英文地址,請注明出處:http://en.pswp.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掃描網段&…