一 前言
這個是續上一篇基於Springboot、Java的SSO單點登陸系統的簡單實現後續的篇章,因爲長度有點長,爲了提高閱讀體驗,就拆分成兩篇了。如果還沒有看過上篇的朋友建議先看一下,文末會給出GitHub地址。
(1)使用環境:
SpringBoot2.X
MyBatis
基於redis存儲的springSession
(2)基礎學習:
關於SSO的基礎學習可以參考該文章:單點登錄(SSO),從原理到實現
代碼風格使用的是曉風輕的代碼規範,對於其中的AOP實現此處不會給出代碼,具體可以在文章尾部的gitHub上查看:我的編碼習慣 - Controller規範
進階可以參考:單點登錄(一)-----理論-----單點登錄SSO的介紹和CAS+選型
(3)目標
- 使用Header認證替換Cookie,避免用戶禁用cookie導致登陸失效的情況
- 實現可以運行操作的SSO單點登錄系統
(4)注意:
- 此處使用了一個項目來模擬一個Client與一個Server,因爲Server依靠存儲token來判斷用戶是否登陸,而Client依靠Session判斷用戶是否登陸,因此兩者能在同個項目共存。
- 由於項目的依賴很多,所以不會事無鉅細地講,只會挑重點的看,具體的可以在文章尾部的GitHub上查看
看完以上文章之後總結一下,在這次簡單實現中我們需要做到的有以下幾點:
- 用戶從Client服務器發起註銷請求。
- Client服務器需要將用戶的註銷請求發往SSO認證中心
- SSO認證中心註銷掉所有Client服務器中的局部會話並且註銷SSO認證中心中存放的token
二 正文
一些基礎的工具類已經在上一篇文章中提出過了,這次直接進入正題:
用戶的註銷主要需要實現:
- 在所有子系統中註銷用戶的局部會話
- 在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