版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/zhangjingao/article/details/89052764
前言
前面我寫了一篇文章,java實作完全跨域SSO單點登入,最後我會比較兩種方案。
那篇文章主要說明完全跨域SSO單點登入的實作,但是我最終并沒有使用那篇,當然,那篇完全可以實作SSO跨域,但是那篇有一些不太優雅的地方,我綜合我的場景等各方面考慮,最終選擇了我下面的這個方案。因為那篇并沒有被選用,是以代碼大家可以随意看,但是下面這個方案因為代碼已經在使用,是以不太友善分享代碼,見諒。大家有什麼問題,歡迎留言或者qq私我,如果是qq(1763608200)私我,建議加驗證資訊:sso探讨(内心os: 不加驗證資訊我不會通過的,最近資訊洩露太嚴重了,好多頭像是雜七雜八的不知道是賣茶還是幹啥的老加我 )
同父域單點登入
什麼是SSO
SSO(Single Sign On)單點登入是實作多個系統之間統一登入的驗證系統,簡單來說就是:有A,B,C三個系統,在A處登入過後,再通路B系統,B系統就已經處于了登入狀态,C系統也是一樣。舉個生活中栗子:你同時打開天貓和淘寶,都進入login界面,都要求你登入的,現在你在淘寶處登入後,直接在天貓處重新整理,你會發現,你已經登入了,而且就是你在淘寶上登入的使用者。說明他們實作了SSO,并且持有相同的資訊。
當然這個特性意味着它的使用場景是:同一公司下的不同子系統,因為對于SSO來說,每一個子系統擁有的資訊都一樣,是使用者的全部資訊,如果是不同公司,那這肯定不合适。
這套方案我們要實作的效果
假設此時有A,B,C三套系統,他們有相同的頂級域名,就是說他們是同一個域名的子域名,此處使用a.xxx.com,b.xxx.com,c.xxx.com來表示,有一個獨立的驗證中心,使用sso.xxx.com表示。當我們在a.xxx.com下登入後,我目前的浏覽器通路b.xxx.com依然能直接通路,并且已經處于登入狀态。當我在A系統中登出登入時,B系統也會被登出登入。但是如果是b.yyy.com在該方案下就不支援了。
具體流程

初始A,B,C全部處于未登入狀态,沒有令牌(tokenid)。
1、使用者通路A系統,沒有令牌,A系統驗證未登入,直接傳回A的登入界面;
2、A輸入賬号密碼進行登入請求到SSO中(附帶重定向url);
3、然後SSO驗證使用者資訊正确,生成身份令牌,将身份令牌轉為jwt,将使用者資訊使用AES加密,将jwt身份令牌和使用者資訊作為鍵值對存進redis,時間設為30分鐘,将身份令牌種進cookie,重定向回攜帶的url(到達了子系統);
4、子系統發現有身份令牌,發送請求到達SSO驗證令牌真僞;
5、SSO收到驗證請求,驗證通過,傳回AES加密的使用者資訊;
6、子系統收到通過資訊,将使用者資訊解密,存進session一分鐘(token),跳轉頁面至首頁或使用者請求頁;
7、一分鐘失效後或者子系統通路時,發現有令牌但session中無資訊,再次重複4,5,6。
解釋幾個疑問點
1、sso中redis存儲的是全局會話,而每個子系統中session存儲的是臨時會話。
2、為什麼臨時會話定義一分鐘?
定義一分鐘,就是為了當A系統退出後,登出了本地臨時會話,再到sso登出全局會話,一分鐘的話即使不去登出B系統的臨時會話,也很快就會消失,這個時間可以忽略,再者一分鐘可以保證子系統和sso實時保證使用者資訊的統一,假設此時該使用者修改了個人資訊,其他系統也能保證盡快得到最新的資訊。
3、為什麼sso中要用redis存儲身份令牌呢?為什麼不用session呢?
首先各個子系統驗證身份令牌時是由各個子系統發出的,不一定是登入的那個域名了,是以無法保證session還能跟登入的那個session一緻,是以使用redis儲存,這樣身份令牌和sso的redis之間也模拟出來一個全局會話關系出來。
4、為什麼要用JWT呢?
是為了實作數字簽名,如果黑客竊取到身份令牌,暴力枚舉令牌,是有機會洩露的,使用JWT後,sso可以驗證這是否被篡改。
5、為什麼要重定向回子系統?
首先A系統登入時是在自己的登入界面,然後直接送出到sso驗證中心,這個時候這個請求是浏覽器和sso建立的聯系,跟子系統沒關系,直接傳回或者轉發什麼的是沒辦法回到子系統的。
6、為什麼登入要直接送出到SSO驗證中心?(内心os:我感覺我快成了杠精,可能我适合去工地,2333 )
因為身份令牌要儲存在浏覽器用戶端,儲存在cookie中,也就是說sso此時要拿到浏覽器請求才能給浏覽器的父域種下cookie,如果是通過子系統再送出請求,那樣是種不了cookie的,當然如果是登入請求送出到子系統,子系統發出驗證,由子系統去種cookie也是可以的,但是這樣的話你内聚就降低了很多,耦合度也高了,子系統原本不用管的登入的事也要管了,sso原本要全管的登入被别人幹了一部分。
簡單看下代碼
SSO的統一登入代碼
/**
* 統一驗證登入中心
* @param username 使用者名
* @param password 密碼
*/
@PostMapping
public void checkLogin(String username, String password, String returnUrl,HttpServletResponse response) {
TbUser user = userService.login(username, password);
log.info("user: "+user);
String jwtValue = null;
if (user != null) {
/*
* 獲得uuid,作為tokenId(TGC)
* 将tokenId存進jedis,傳回用戶端以jwt存儲的tokenId,
* 即使tokenId被截取也無所謂
*/
String tokenId = UUID.randomUUID().toString();
//生成jwt
jwtValue = new JwtUtil(tokenId, null).creatJwt();
log.info("tokenId: " + tokenId+" jwt: "+jwtValue);
if (jwtValue != null) {
Jedis jedis = jedisPool.getResource();
jedis.set(tokenId, user.toString());
jedis.expire(tokenId,1800);
log.info("檢視key的剩餘生存時間:"+jedis.ttl(tokenId));
}
}
//将jwt加密的TGC存進cookie
Cookie cookie = new Cookie("tokenId", jwtValue);
cookie.setPath("/"); //設定根域名
cookie.setHttpOnly(true);
response.addCookie(cookie);
try {
response.sendRedirect(returnUrl);
} catch (Exception e) {
e.printStackTrace();
}
}
子系統使用身份令牌驗證
/**
* 驗證wlId
* 用jedis的原因是不用的話無法從所有session會話中找到sessionid為tokenid的會話
* @param wlId wlId
* @return 如果失效傳回null,反之傳回aes加密的使用者資訊
*/
@GetMapping
public String tokenCheck (String wlId) {
//從jedis中獲得token
Jedis jedis = jedisPool.getResource();
String user = null;
if (wlId != null) {
wlId = new JwtUtil(null, wlId).decryptJwt();
user = jedis.get(wlId);
jedis.expire(wlId,1800);
log.info("tokencheck檢視key的剩餘生存時間:"+jedis.ttl(wlId));
}
return user;
}
登出的方法
/**
* 清除全局會話,子系統跳轉到回到登入頁的方法
* @param wlId 身份令牌
*/
@GetMapping("/loginout")
public void loginOut(String wlId) {
log.info("clear token");
Jedis jedis = jedisPool.getResource();
jedis.del(wlId);
}
與我另一篇完全跨域sso對比
首先附上篇文章連結:java實作完全跨域SSO單點登入
1、在以後你們的系統可能短時間内不會使用多個根域名的話,建議使用該版本或者修改該版本。
2、兩者都可以實作單點登入,我已經自己試驗過了。并且另一個版本有github代碼提供。
3、上篇是完全跨域SSO,即使以後使用不同的域名,完全沒問題。該版本隻适用于同父域單點登入。
4、上篇雖然是完全跨域sso,但是sso需要多元護一個子系統表,用來記錄向子系統添加cookie的連結,該篇不需要。
5、當以後使用者量大,業務拓展的時候,第一個登入的子系統不僅要使用更多的時間驗證使用者是否正确,并且還要花時間對那麼多個子系統進行重定向種cookie,雖然可以先跳轉到使用者首頁再去請求,但是也會造成第一個登陸的登入速度慢一些,使用者體驗可能就不那麼好。
在這個版本上補充完全跨域SSO内容
這個補充是我後來想的,我并未實踐,但是我覺得可行,歡迎留言或加q探讨
先看下草圖
有a,b兩個系統,域名為a.com,b.com。然後sso為 sso.com。首先都處于未登入狀态a系統和b系統都是自己獨立的登入界面。
1、a首先去請求,然後a的server發現未登入,這個時候首先重定向sso.checkTokenId,這樣sso發現cookie下确實沒有種下身份令牌這樣就重定向回a.login界面
2、a在登入界面送出賬号密碼到達sso.login方法,sso驗證資訊正确,那麼就為sso.com的cookie種下身份令牌這個就是全局會話,重定向回最初的通路連結,并攜帶身份令牌,在a處生成了局部會話。
3、現在用戶端再去通路b.com下的頁面,這個時候b.server發現沒有局部會話,未登入,這個時候首先重定向到sso.checkTokenId,sso驗證是否有全局的身份令牌
4、sso驗證有身份令牌,那麼就傳回b最初的通路連結,攜帶身份令牌,在b處生成局部會話,b處也就成了已登入
5、sso驗證沒有身份令牌,那麼就重新到b.com下的b.login界面,重複2步驟,最終生成全局會話和局部會話
涉及到的問題
1、每次重定向攜帶資訊,這個時候可以使用302重定向跳轉,可以實作get重定向并攜帶參數
2、sso系統加了一個checkTokenId方法就是精華,這樣每次判斷是否有會話,就可以重定向到此獲得sso.com的cookie,這樣就可以得到全局的身份令牌
3、此時各個子系統使用的是不同的登入界面,如果想要使用統一的登入界面,依然要采用本設計,因為如果隻是單純的靠統一的登入界面來獲得sso.com的全局會話,這樣做不到使用者無感覺,加一個方法就是為了不做頁面跳轉。