title: 基於SpringBoot+Redis的Session共用與單點登錄 date: 2019 07 23 02:55:52 categories: 架構 author: mrzhou tags: SpringBoot redis session 單點登錄 基於SpringBoot+Redi ...
title: 基於SpringBoot+Redis的Session共用與單點登錄
date: 2019-07-23 02:55:52
categories:
- 架構
author: mrzhou
tags: - SpringBoot
- redis
- session
單點登錄
基於SpringBoot+Redis的Session共用與單點登錄
前言
使用Redis來實現Session共用,其實網上已經有很多例子了,這是確保在集群部署中最典型的redis使用場景。在SpringBoot項目中,其實可以一行運行代碼都不用寫,只需要簡單添加添加依賴和一行註解就可以實現(當然配置信息還是需要的)。
然後簡單地把該項目部署到不同的tomcat下,比如不同的埠(A、B),但項目訪問路徑是相同的。此時在A中使用set方法,然後在B中使用get方法,就可以發現B中可以獲取A中設置的內容。
但如果就把這樣的一個項目在多個tomcat中的部署說實現了單點登錄,那就不對了。
所謂單點登錄是指在不同的項目中,只需要任何一個項目登錄了,其他項目不需要登錄。
同樣是上面的例子,我們把set和get兩個方法分別放到兩個項目(set、get)中,並且以集群方式把兩個項目都部署到伺服器A和B中,然後分別訪問A伺服器的set和B伺服器的get,你就會發現完全得不到你想要的結果。
同一項目中的set/get
依賴添加就不說了,直接使用最簡單的方式
@SpringBootApplication
@EnableRedisHttpSession
@RestController
public class SessionShareApplication {
public static void main(String[] args) {
SpringApplication.run(SessionShareApplication.class, args);
}
@Autowired
HttpSession session;
@Autowired
HttpServletRequest req;
@GetMapping("/set")
public Object set() {
session.setAttribute("state", "state was setted.");
Map<String, Object> map = new TreeMap<>();
map.put("msg", session.getAttribute("state"));
map.put("serverPort", req.getLocalPort());
return map;
}
@GetMapping("/get")
public Object get() {
Map<String, Object> map = new TreeMap<>();
map.put("msg", session.getAttribute("state"));
map.put("serverPort", req.getLocalPort());
return map;
}
}
將該項目打war包,分別部署在tomcatA(埠8080),tomcatB(埠8081),然後通過tomcatA/set 方法設置session,再使用 tomcatB/get 方法即可獲得session的值。但這隻是實現了同一項目session的共用。並不是單點登錄。
為了驗證,我們不仿將set/get方法拆分為兩個項目。
拆分set/get為兩個項目
- get項目
@SpringBootApplication
@EnableRedisHttpSession
@RestController
public class SetApplication {
public static void main(String[] args) {
SpringApplication.run(SetApplication.class, args);
}
@Autowired
HttpSession session;
@Autowired
HttpServletRequest req;
@GetMapping("/")
public Object set() {
session.setAttribute("state", "state was setted.");
Map<String, Object> map = new TreeMap<>();
map.put("msg", session.getAttribute("state"));
map.put("serverPort", req.getLocalPort());
return map;
}
}
將該項目打包為set.war
- set項目
@SpringBootApplication
@EnableRedisHttpSession
@RestController
public class GetApplication {
public static void main(String[] args) {
SpringApplication.run(GetApplication.class, args);
}
@Autowired
HttpSession session;
@Autowired
HttpServletRequest req;
@GetMapping("/")
public Object get() {
Map<String, Object> map = new TreeMap<>();
map.put("msg", session.getAttribute("state"));
map.put("serverPort", req.getLocalPort());
return map;
}
}
將該項目打包為get.war
再分別將set.war,get.war部署在tomcatA和tomcatB,再通過 tomcatA/set 設置session內容, 然後通過 tomcatB/get 就發現無法獲得session的值。
問題分析
儘管我們使用的路徑都是一樣的,但其實是兩個項目,與前面的一個項目是完全不同的,問題就在於 session和cookie在預設情況下是與項目路徑相關的,在同一個項目的情況下兩個方法所需要的cookie依賴的項目路徑是相同的,所以獲取session的值就沒有問題,但在後一種情況下,cookie的路徑是分別屬於不同的項目的,所以第二個項目就無法獲得第一個項目中設置的session內容了。
解決方法
解決方法在springboot項目中其實也非常簡單。既然cookie路徑發生了變化,那我們讓它配置為相同的路徑就解決了。
在每個子項目中都添加一個配置類或者直接設置cookie的路徑,如果有功能變數名稱還可以設置功能變數名稱的限制,比如 set.xxx.com 與 get.xxx.com 這種情況與我們就需要設置cookie的功能變數名稱為 xxx.com,以確保無法在哪個項目下都能夠獲取 xxx.com 這個功能變數名稱下的cookie值。這樣就確保能夠正常獲得共用的session值了。
@Configuration
public class CookieConfig {
@Bean
public static DefaultCookieSerializer defaultCookieSerializer() {
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setCookiePath("/");
//serializer.setDomainName("xxx.com"); //如果使用功能變數名稱訪問,建議對這一句進行設置
return serializer;
}
}
以上才是正直的redis實現單點登錄的正確打開方式。