多項目共享session實戰

這周工作中遇到的一個問題就是要把組內的兩個項目(完全不同的兩個項目,沒有任何關聯那種)做成統一入口。這裏用A,B來代指兩個項目。
現在要做成去掉B的登錄,然後在A的項目裏用iframe的形式把B的頁面嵌入到A項目中。首先這兩個項目的權限是單獨在另一個平臺獲取的。所以理論上這種實現是完全可行的。但是具體的實現其實一步一個坑。當然了有一些也是我個人問題。下面從頭開始講思路。

第一步思路:Session從內存中移出

首先簡單介紹下我這裏爲什麼會說到Session共享。因爲之前兩個項目都是基於session實現的鑑權。所以出於最小改動原則,我這裏還是要繼續使用session而非JWT。
然後說到兩個項目的鑑權都是用session實現的,但是基於內存的session肯定是無法實現共享。所以這裏第一步是換成redis-session。

redis-session的使用

兩個項目都是spring項目,所以大大簡化了引入redis-session的過程。主要分散步:

1. 導入依賴

        <!-- 引入 spring-session 依賴 -->
        <dependency>
            <groupId>org.springframework.session</groupId>
            <artifactId>spring-session-data-redis</artifactId>
        </dependency>
        <!-- 引入 redis 依賴 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

需要注意的是如果我們只是使用redis存儲session,是不需要引入下面的redis依賴的。我這裏之所以引入這個依賴是爲了對redis進行一些查詢操作。

2. 添加redis配置

關於redis的配置,最基本的連接配置,還有一個很重要的註解,就是session的存儲方式是redis。如下:

 spring:
  session:
    store-type: redis #session 存儲類型
  redis:
    database: 2
    host: 127.0.0.1
    port: 6379

因爲我這裏是測試環境,所以也沒設置密碼。如果你的redis還想有一些別的配置按需自己配吧。

3. 啓動類添加註解

@EnableRedisHttpSession  //開啓使用redis存儲session

只需要這三步,我們就會發現session存入redis中了。我們可以用測試代碼試一下,請求中隨便set到session中點東西。

    @GetMapping("/test")
    public Object test(HttpServletRequest request){
        request.getSession().setAttribute("test",12);
        return null;
    }

然後我們打開redis可視化軟件看一下是不是增加了一個session:


到了這裏,session存到redis中已經實現,兩個項目都改好之後,該考慮如何共享了。

第二步思路:共享redis中的session

試錯一:假裝是一個session

到了這裏網上開始有參差不齊的聲音了。一種是說什麼判斷是不是一個session是根據cookie中傳的session值,然後我就信了。當時第一反應,讓兩個項目的cookie中的session值是一樣的不就行了麼


然後我本來是打算獲取A項目中請求的session,然後傳給B項目的一個接口。讓前端把這個值設置成session的值。
一頓操作後發現不可行。最後想到一個很好的測試方法:A登錄完成後獲取到session,我直接在postman中調用B服務的接口,將session的值寫入。請求後發現request.getSession(false)是空。所以根據說明這個判斷方式完全行不通。
反正我也不知道是我操作問題還是這個說法本來就是假的,如果有實測成功了的朋友歡迎來辯

試錯二:nginx轉發

這是我們組的一個大佬提出來的思路。就是前端請求都用A服務的域名包裝一下,這樣session肯定是一致的。
首先在B項目的前端配置:請求路徑改成a.com/bbb/xxx
這裏的a.com是A項目的請求地址,/bbb是自定義的一個基礎前綴。爲了區分實際上的b項目的請求。 /xxx是原本b項目接口的請求路徑。
比如原本b的一個接口: b.com/user/info
現在改成: a.com/bbb/user/info

然後在A項目的nginx中配置一個轉發:



因爲我們項目的私密性,所以這裏貼出來的是百度的轉發方法。其實就是設置/bbb 的請求轉發到b項目原本的後端地址上。
理論上請求是同一個地址出來的,session應該是一個了吧。然後運行項目一跑,發現轉過去的還是沒有session。
後來百度是因爲域名不同,自動清了cookie。因爲nginx這塊的配置一直都是我一個同事來的。所以到此這個想法就失敗了。
當然我之前百度說有一種方式可以跨域名,事實證明這種配置沒啥用。不確定是不是因爲我們這邊前端有什麼特殊處理(因爲瀏覽器查看的session和存到redis中的sessionId不一致)

location ~ /xxx/ {
proxy_cookie_domain b.com a.com;
proxy_pass http://b.com;
}

勉強實現三: 以A模塊爲核心代碼維護B模塊的session

這裏其實不算是試錯了,但是實現的方式我自己挺不滿意的。當然了時間緊任務重,目前這個功能是這麼實現的。
因爲需求的前提是A,B兩個項目會存在session過期時間不一致。而且B是掛在A項目裏的,這就有個問題,B項目session失效的話,其實是沒辦法跳到登錄頁的。所以要保持session過期時間一致。所以如果不能共享的話,手動維護兩個session有效性一致也可以。
這也就是我上面引入依賴的時候要引入data-redis的原因啦。
這個代碼維護就簡單的很。獲取A項目的sessionId。然後放在B項目請求的header中.然後在b項目中做判斷:

  • A有session,B也有session,放行
  • A沒有session,B也沒有session,放行
  • A沒有session,B有sesion, B的session刪除
  • A有session,B沒有session,A的session刪除

以上四個邏輯可以保證A,B的session要麼同時生效,要麼同時不生效。代碼邏輯其實也很簡單,我就截圖的形式展現下。


另外使用redis的時候還有有個小插曲,最開始我用的是RedisTemplate。當時也就圖個簡單,結果發現獲取不到key。最後發現這種帶有:的層級結構的key,不能直接取出。哪怕用redisTemplata.key("*")也獲取不到。當然了也存不了。
估計是有什麼用法, 但是因爲我以前項目中我知道層級結構的是可以取的,所以找了半天原因最後換成StringRedisTemplate就可以了。
還有一個就是默認的redis-session存session的時候序列化有點問題,取出來的不是存進去的字符串,這是因爲默認會採用jdk的序列化方式。這個單獨設置下序列化方式就行了。

到這裏其實讓A,B僞共享session就算實現了。之所以說僞共享是因爲只做到了一個沒有另一個自動刪除。更合理的應該是一個存在另一個自動創建。這個其實也容易實現。就是發現其中一個存在自動調用另一個的登錄接口創建session,我這裏圖簡單也沒寫。我上面說覺得這種方式不太滿意是每次都校驗其實挺沒必要的。但是目前也沒找到更合適的方式。
本篇筆記就記到這裏,如果稍微幫到你了記得點個喜歡點個關注。如果大家有更好的實現方式歡迎提出,或者有什麼建議也行,共同進步嘛!也祝大家工作順順利利!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章