基於Springboot的SSO單點登陸系統的註銷操作實戰

一 前言

這個是續上一篇基於Springboot、Java的SSO單點登陸系統的簡單實現後續的篇章,因爲長度有點長,爲了提高閱讀體驗,就拆分成兩篇了。如果還沒有看過上篇的朋友建議先看一下,文末會給出GitHub地址。

(1)使用環境:

SpringBoot2.X

MyBatis

基於redis存儲的springSession

(2)基礎學習:

關於SSO的基礎學習可以參考該文章:單點登錄(SSO),從原理到實現

代碼風格使用的是曉風輕的代碼規範,對於其中的AOP實現此處不會給出代碼,具體可以在文章尾部的gitHub上查看:我的編碼習慣 - Controller規範

進階可以參考:單點登錄(一)-----理論-----單點登錄SSO的介紹和CAS+選型

(3)目標

  1. 使用Header認證替換Cookie,避免用戶禁用cookie導致登陸失效的情況
  2. 實現可以運行操作的SSO單點登錄系統

(4)注意:

  1. 此處使用了一個項目來模擬一個Client與一個Server,因爲Server依靠存儲token來判斷用戶是否登陸,而Client依靠Session判斷用戶是否登陸,因此兩者能在同個項目共存。
  2. 由於項目的依賴很多,所以不會事無鉅細地講,只會挑重點的看,具體的可以在文章尾部的GitHub上查看

看完以上文章之後總結一下,在這次簡單實現中我們需要做到的有以下幾點:

  1. 用戶從Client服務器發起註銷請求。
  2. Client服務器需要將用戶的註銷請求發往SSO認證中心
  3. SSO認證中心註銷掉所有Client服務器中的局部會話並且註銷SSO認證中心中存放的token

二 正文

一些基礎的工具類已經在上一篇文章中提出過了,這次直接進入正題:

用戶的註銷主要需要實現:

  1. 在所有子系統中註銷用戶的局部會話
  2. 在SSO認證中心中註銷用戶的token信息

在controller層:

	
	// SSO認證中心
	/**
     * 註銷用戶在所有子系統的登陸狀態
     * @param requestBean token
     * @return 操作結果
     */
    @PostMapping("/logout")
    public ResultBean<Data> logout(@RequestBody RequestBean requestBean) {
        return new ResultBean<>(userService.logout(requestBean));
    }


	// Client服務器
    /**
     * 註銷局部會話,若請求方不爲SSO認證中心,則請求認證中心註銷所有子系統的登陸狀態
     * @param requestBean token
     * @param request 請求
     * @return 操作結果
     */
    @PostMapping("sublogout")
    public ResultBean<Data> subLogout(@RequestBody RequestBean requestBean, HttpServletRequest request) {
        if (!request.getRemoteAddr().startsWith("127.0.0.1")) {
            try {
                return new HttpClientUtil().postAction("http://localhost:8889/user/logout", new RequestBean());
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            return new ResultBean<>(userService.subLogout(requestBean));
        }
        return new ResultBean<>();
    }	

可以注意到在客戶端中,對接收到的請求進行了判斷,若不是來自SSO認證中心的註銷登錄狀態請求,則轉發請求到SSO認證中心,由SSO認證中心來驗證並決定是否註銷所有子系統的登陸狀態。

既然如此,那我們就先從SSO認證中心的註銷功能開始看起:

在UserService接口中:

		/**
     * 註銷用戶在所有子系統的登陸狀態
     * @param requestBean token
     * @return 操作結果
     */
    Data logout(RequestBean requestBean);

	/**
     * 註銷局部會話,若請求方不爲SSO認證中心,則請求認證中心註銷所有子系統的登陸狀態
     * @param requestBean token
     * @return 操作結果
     */
    Data subLogout(RequestBean requestBean);

下面從SSO認證中心logout()的實現方法進入:

	/**
     * 驗證token是否存在,若存在則請求註銷所有子系統的局部變量並且銷燬token
     * @param requestBean token 令牌憑證
     * @return 操作結果
     */
    @Override
    public Data logout(RequestBean requestBean) {
        String token = requestBean.getToken();
        log.info("logout() : token = {}", token);
        if (tokenAndUrlMap.containsKey(token)) {
            List<String> urls = tokenAndUrlMap.get(token);

            // 註銷所有子系統的登陸狀態
            for (String clientUrl : urls) {
                logoutSubSystem(token, clientUrl);
            }
            // 移除用戶登陸狀態
            tokenAndUrlMap.remove(token);
            tokenAndUserMap.remove(token);
            tokenAndSessionId.remove(token);
            // 默認成功
            return null;
        }
        throw new CheckException("令牌錯誤");
    }

​ 先對令牌的合法性進行驗證,驗證通過後通過HttpClient發送請求註銷所有子系統的局部變量。

發送銷燬子系統局部變量請求:

	private void logoutSubSystem(String token, String clientUrl) {
        try {
            log.info("logoutSubSystem(): sessionId = {}", tokenAndSessionId.get(token));
            httpClientUtil.postAction(clientUrl + "/user/sublogout", new RequestBean().setAuthToken(tokenAndSessionId.get(token)));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

此時就像子系統發送了銷燬局部變量的請求。那麼下面讓我們看下子系統的操作。

後面的操作中我採用了一個銷燬Session的其他方法,就是使用redis來單獨銷燬該session的user屬性。(也可以通過將x-auth-token放置於header中使得服務器能夠找到相應的session,這樣就直接在controller層操作session的銷燬就行了,不過這次我採用的不是這種方法)

從上述的controller層我們可以發現子系統並沒有銷燬局部變量,那麼是怎麼銷燬的呢。看到子系統的serviceImpl中:

	/**
     * 驗證來自服務器的token與clientUrl,
     * @param requestBean token、clientUrl
     * @return 操作結果,成功data爲帶token與clientUrl
     */
    @Override
    public Data subLogout(RequestBean requestBean) {
        String xAuthToken = requestBean.getAuthToken();
        log.info("subLogout() : xAuthToken = {}", xAuthToken);
        if (isNotEmpty(xAuthToken)) {
            return subLogoutImpl(xAuthToken);
        }
        throw new CheckException(UserStatusEnum.PARAMETER_ERROR.getMsg());
    }
	/**
     * 通過redis直接刪除局部變量在redis數據庫中的user屬性
     * @param xAuthToken x-auth-token 相當於SESSIONID
     * @return 操作成功
     */
    private Data subLogoutImpl(String xAuthToken) {
        redisTemplate.opsForList().getOperations().delete("spring:session:sessions:" + xAuthToken);
        return new Data();
    }

這樣就能操作成功了。

下面給出logout()與sublogout()的json格式:

{
	"user":{
		"account":"1",
		"password":"1"
	},
	"token":"ce263a4d-23a8-4b5a-9dab-0690b4f6aaf5"
}

三 總結

在用戶註銷一個子系統的登陸狀態時,爲了不出現用戶註銷後在其他子系統中也能夠登陸的情況,於是需要SSO認證中心將所有已註冊的子系統中的用戶登陸狀態都進行註銷。

於是在上述中,我們將用戶token的驗證與判斷移交到SSO認證中心,當子系統接收到註銷請求的時候,都會直接把該請求移交到SSO認證中心中,再由SSO認證中心統一進行子系統的註銷。

項目GitHub地址:https://github.com/attendent/distrubuted

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