目錄
3.1.3.在說跨域SSO流程之前要提前說幾個CAS知識點:
3.1.6.代碼實現跨域SSO(基於CAS-Client):
1.什麼是單點登錄?
大家都知道百度不僅僅只有搜索引擎這一個業務,它還有百度貼吧,百度雲盤等衆所周知的業務。而且有沒有發現?你只需要在百度任意一個業務中登陸過,其他業務都默認自動登錄了你這個賬號呢?比如你在百度搜索中登錄了你的賬號,你再進入百度貼吧或者百度網盤,顯示你已經登錄了。
百度首頁登錄:
在登錄這個“進階的小名”賬號的情況下,分別打開百度網盤和百度地圖的效果:
百度網盤和百度地圖都默認登錄了“進階的小名”這個賬號了。相對的,如果你在任何一個業務中退出登錄,其他的業務也會自動退出當前賬號的登錄。
2.單點登錄的前世與今生——前世篇(SSO)
2.1.單點登錄的定義是:
單點登錄(英語:Single sign-on,縮寫爲 SSO),又譯爲單一簽入,一種對於許多相互關連,但是又是各自獨立的軟件系統,提供訪問控制的屬性。當擁有這項屬性時,當用戶登錄時,就可以獲取所有系統的訪問權限,不用對每個單一系統都逐一登錄。這項功能通常是以輕型目錄訪問協議(LDAP)來實現,在服務器上會將用戶信息存儲到LDAP數據庫中。相同的,單一退出(single sign-off)就是指,只需要單一的退出動作,就可以結束對於多個系統的訪問權限。
2.2.總結成簡單一句話說就是:
在多個應用系統中,用戶只需要登錄一次就可以訪問相互信任的應用系統。
2.3.單點登錄的優點:
不管是百度還是我們自己公司的項目,隨着時間的推移,我們都需要根據市場需求或是客戶新的需求退出新的功能,不可能在每一個新加的模塊中都寫一遍登錄的業務,於是單點登錄解決了這個問題,將登錄模塊從其他業務模塊中抽離出來。
2.4.拿去遊樂園買票來講一下單點登錄的簡單實現機制:
我們95後的天津孩子應該都去過一個叫“樂園”的遊樂場,這個遊樂場裏面有:“碰碰車”、“旋轉木馬”、“叢林鼠”、“摩天輪”等遊樂設施,這麼多的遊樂設施,如果家長想帶着熊孩子挨個兒玩一遍是不太可能了,爲啥啊?還不是因爲排隊買票浪費時間嗎!人多的時候,玩兒倆設施就家走了😂。排一個小時“碰碰車”的隊,孩子進去玩10分鐘出來,孩子是美了,一看,家大人又去旁邊“激流勇進”排隊去了,針對這種熊孩子,“樂園”就推出了通票這一制度。通票就是:“樂園”大門口買一張通票(阿姨給你手腕戴上一個印着通票蓋着戳的這麼一個紙圈),進到裏面家大人就不用單獨給熊孩子排隊挨個設施買票了,家大人找個樹蔭下一呆,看着熊孩子帶着那個紙圈滿園子跑就完了😂。
2.5.單點登錄的實現機制:
如下圖所示,當用戶第一次訪問系統1的時候,因爲還沒有登錄,會被引導到認證系統中進行登錄。
- 根據用戶提供的登錄信息,認證系統進行身份效驗,如果通過效驗,應該返回給用戶一個認證的憑據Ticket;
- 用戶再訪問其他相互信任的應用的時候,就會把這個Ticket作爲自己認證的憑據,應用系統接受到請求之後會把 Ticket送到認證系統進行效驗,檢查 Ticket的合法性;
- 如果通過效驗,用戶就可以在不用再次登錄的情況下訪問系統2和系統3了。
2.6.單點登錄的分類:
我將會把單點登錄分爲“同域SSO”和“跨域SSO”兩部分說:(“跨域SSO” 會在後文 單點登錄的前世與今生——今生篇(CAS)中提到)
2.7.同域SSO:
同域SSO又分爲 : “同域SSO” 和 “同父域SSO”
2.7.1.同域SSO概念:
沒有設置獨立的 SSO 服務器,因爲業務後臺服務器本身就足以承擔 SSO 的職能。
2.7.2.同父域SSO概念:
和同域SSO不同在於,服務器在返回 cookie 的時候,要把cookie 的 domain 設置爲其父域。
2.7.3.同域SSO實現流程圖:
同域SSO:
- 用戶點擊http://www.xiaoming.com頁面登錄按鈕,向後臺服務器發送登錄請求;
- 輸入正確的用戶名密碼,登錄認證成功,服務器會把登錄信息寫入session;
- 服務器爲該用戶生成一個cookie,並加入到response header中,隨着請求返回而寫入瀏覽器中;
- 用戶再次訪問同域的http://cart.xiaoming.com的時候,瀏覽器會帶上之前的cookie;
- 後臺服務器通過該cookie驗證當前賬戶的登陸狀態了。
同父域SSO:
- 同父域 SSO 是同域 SSO 的簡單升級,唯一的不同在於,服務器在返回 cookie 的時候,要把cookie 的 domain 設置爲其父域。
- 比如兩個產品的地址分別爲 http://www.xiaoming.com 和 http://cart.xiaoming.com,那麼 cookie 的域設置爲 xiaoming.com 即可。在訪問兩個資源的時候,這個 cookie 都能發送到服務器,本質上和同域 SSO 沒有區別。
2.7.4.代碼實現同域SSO:
接下來我通過一個Demo來演示同域SSO的要整過程。這個Demo模擬的是利用cookie實現的同域單點登錄:(因爲我的前後端打包部署在一個Server裏,而且瀏覽器用同一個Origin請求前端和服務器端,即使端口號不同,也不存在跨域問題。)
先上效果:
我在Hosts文件裏面進行了如下配置(模擬):
127.0.0.1 www.xiaoming.com
127.0.0.1 vip.xiaoming.com
127.0.0.1 cart.xiaoming.com
127.0.0.1 login.xiaoming.com
本文只展示部分重要代碼(點擊獲取同域SSO源碼(導入項目時注意:本項目是Gradle構建的多模塊項目))
登錄模塊後端處理登錄業務代碼:
@Controller
@RequestMapping("/login")
public class LoginController {
// 模擬數據庫用戶數據
public static Set<User> dbUser;
static {
dbUser = new HashSet<>();
dbUser.add(new User(0, "zhangsan", "1"));
dbUser.add(new User(1, "lisi", "2"));
dbUser.add(new User(2, "wangwu", "3"));
}
//處理“/login”下的post請求
@PostMapping
public String doLogin(User user, HttpSession session, HttpServletResponse response) {
System.out.println(dbUser);
String target = (String) session.getAttribute("target");
System.out.println(target);
/**
* 此處爲lambda表達式(源碼中又完整解釋)
*/
Optional<User> first = dbUser.stream().filter(dbUser -> dbUser.getUsername().equals(user.getUsername()) &&
dbUser.getPassword().equals(user.getPassword()))
.findFirst();
//判斷用戶是否登錄
if (first.isPresent()) {
//保存用戶登錄信息
String token = UUID.randomUUID().toString();//隨機生成一個token,UUID(全局唯一標識符)
Cookie cookie = new Cookie("TOKEN", token);
//cookie要在子系統之間互相訪問,要在同一個域下
cookie.setDomain("xiaoming.com");
response.addCookie(cookie);
//模擬緩存,把User和隨機生成的cookie存(put)到“loginUser”方法中的map
LoginCacheUtil.loginUser.put(token, first.get());
} else {
//登陸失敗
session.setAttribute("msg", "用戶名或密碼錯誤");
return "login";
}
//重定向到target地址
return "redirect:" + target;
}
/**
* 給其他子系統開放一個接口,通過token獲取登錄用戶的信息
* @param token
* @return
*/
@GetMapping("/info")
@ResponseBody
//ResponseEntity <T> ,泛型T 表示要設置的返回的 響應體
public ResponseEntity<User> getUserInfo(String token) {
if (!StringUtils.isEmpty(token)) {
User user = LoginCacheUtil.loginUser.get(token);
return ResponseEntity.ok(user);
} else {
//錯誤請求(我要token,你卻沒給我)
return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);
}
}
}
登錄模塊後端跳轉頁面控制器:
/**
* 頁面跳轉邏輯
*/
@Controller
@RequestMapping("/view")
public class ViewController {
/**
* 跳轉到登錄頁面
* @return
* @RequestParam中 value表示參數名字 required表示是否爲必需,defaultValue表示默認值
* @CookieValue中 有cookie就獲取“TOKEN”沒有就不獲取,cookie不是必須的
*/
@GetMapping("/login")
public String tologin(@RequestParam(required = false, defaultValue = "") String target,
HttpSession session,
@CookieValue(required = false, value = "TOKEN") Cookie cookie) {
/**
* 判斷target是否爲空
* 爲空:主頁面
* 不爲空:存到session
*/
if (StringUtils.isEmpty(target)) {
target = "http://www.xiaoming.com:9010";
}
if (cookie != null) {
/**
* 如果是登錄的用戶再次訪問登陸系統時,就重定向主頁面(target)
*/
String value = cookie.getValue();
//獲取模擬緩存類LoginCacheUtil中拿出對應token的用戶信息
User user = LoginCacheUtil.loginUser.get(value);
if (user != null) {
return "redirect:" + target;
}
}
// else {
//重定向地址
session.setAttribute("target", target);
return "login";
// }
}
}
首頁後端跳轉頁面控制器:
@Controller
@RequestMapping("/view")
public class ViewController {
@Autowired
private RestTemplate restTemplate;
private final String LOGIN_INFO_ADDRESS = "http://login.xiaoming.com:9000/login/info?token=";
@GetMapping("/index")
public String toIndex(@CookieValue(required = false, value = "TOKEN") Cookie cookie, HttpSession session) {
if (cookie != null) {
String token = cookie.getValue();
if (!StringUtils.isEmpty(token)) {
Map result = restTemplate.getForObject(LOGIN_INFO_ADDRESS + token, Map.class);
System.out.println("hahha" + result);
session.setAttribute("loginUser", result);
}
}
return "/index";
}
}
文章篇幅有限,只展示部分代碼,可以 點擊獲取同域SSO源碼 clone到本地自行測試。
3.單點登錄的前世與今生——今生篇(CAS)
3.1.跨域SSO
3.1.1.CAS概念:
集中式認證服務(英語:Central Authentication Service,縮寫CAS)是一種針對萬維網的單點登錄協議。它的目的是允許一個用戶訪問多個應用程序,而只需向認證服務器提供一次憑證(如用戶名和密碼)。這樣用戶不僅不需在登陸web應用程序時重複認證,而且這些應用程序也無法獲得密碼等敏感信息。CAS 是 Yale 大學發起的一個企業級的、開源的項目,旨在爲 Web 應用系統提供一種可靠的單點登錄解決方法。
3.1.2.CAS包含兩個部分:
CAS Server 和 CAS Client
- CAS Server:負責完成對用戶的認證工作 , 需要獨立部署。
- CAS Client:負責處理對客戶端受保護資源的訪問請求,需要對請求方進行身份認證時,重定向到 CAS Server 進行認證。
3.1.3.在說跨域SSO流程之前要提前說幾個CAS知識點:
Cas-Client接口方面:
/login:登錄接口,用於登錄到中心服務器。
/logout:登出接口,用於從中心服務器登出。
3.1.4.理解幾種票根:
1.TGT (Ticket Grangting Ticket) :
TGT 是 CAS 爲用戶簽發的登錄票據,擁有了 TGT,用戶就可以證明自己在 CAS 成功登錄過。TGT 封裝了 Cookie 值以及此 Cookie 值對應的用戶信息。
2.TGC(Ticket Granting Cookie) :
CAS Server 生成TGT放入自己的 Session 中,而 TGC 就是這個 Session 的唯一標識(SessionId),以 Cookie 形式放到瀏覽器端。
3.ST(Service Ticket) :
ST 是 CAS 爲用戶簽發的訪問某一 service 的票據。用戶訪問 service 時,service 發現用戶沒有 ST,則要求用戶去 CAS 獲取 ST。
票據關係:用戶信息簽發TGT,TGT簽發ST,PGT簽發PT。
3.1.5.跨域SSO實現流程圖:
- 用戶訪問產品 CasClientOne,域名是 http://localhost:8081。
- 由於用戶沒有攜帶在CasClientOne上登錄的 cookie,所以 CasClientOne重定向到SSO 服務器的地址。
- 由於用戶沒有攜帶在 SSO 服務器上登錄的 TGC,所以 SSO 服務器判斷用戶未登錄,給用戶顯示統一登錄界面。
- 登錄成功後,SSO 服務器構建用戶在 SSO 登錄的 TGT,同時返回一個 http 重定向(包含 SSO 服務器派發的 ST )。
- 重定向的 http response 中包含寫 cookie。這個 cookie 代表用戶在 SSO 中的登錄狀態,它的值是 TGC。
- 瀏覽器重定向到CasClientOne。此時重定向的 url 中攜帶着 SSO 服務器生成的 ST。根據 ST,CasClientOne向 SSO 服務器發送請求,SSO 服務器驗證票據的有效性。驗證成功後,CasClientOne知道用戶已經在 SSO服務器登錄了,於是CasClientOne構建用戶登錄 session。
- 用戶訪問產品CasClientTWO,域名是 http://localhost:8082。
- 由於用戶沒有攜帶在CasClientTWO上登錄的cookie,所以 CasClientTWO重定向到SSO 服務器,去詢問用戶在 SSO 中的登錄狀態。
- 瀏覽器重定向到 SSO服務器。由於已經向瀏覽器寫入了攜帶 TGC 的cookie,所以此時 SSO 服務器可以拿到,根據 TGC 去查找 TGT,如果找到,就判斷用戶已經在SSO服務器登錄過了。
- SSO 服務器返回一個重定向,重定向攜帶 ST。
- 瀏覽器帶 ST 重定向到CasClientTWO。
- CasClientTWO根據票據向 SSO 服務器發送請求,票據驗證通過後,CasClientTWO知道用戶已經在SSO服務器登錄了,於是生成 session,向瀏覽器寫入CasClientTWO的 cookie。
3.1.6.代碼實現跨域SSO(基於CAS-Client):
先上效果:
本文只展示部分重要代碼(點擊獲取CAS-Demo源碼(導入項目時注意:本項目是Maven構建的多模塊項目))
客戶端One的部分實現:
public class CasClientOne {
@RequestMapping("/CasClientOne")
public String CasClientOne(HttpSession session, HttpServletRequest request){
Principal userPrincipal = request.getUserPrincipal();
String name = userPrincipal.getName();
session.setAttribute("msg",name+"登錄了CasClientOne。。。");
return "CasClientOne";
}
}
代碼使用Springboot集成Cas5.3實現,點擊獲取配置好的Cas-Server,篇幅有限,本文中只展示了一個客戶端的實現,可以 點擊獲取CAS-Demo源碼 Clone到本地自行測試。
關於Cas,後期還會更新一些我在公司項目中使用的(SpringBoot CAS-Client)實際遇到的一些問題的解決方案。感興趣的小夥伴可以點點關注。覺得本文對您理解單點登錄有一些幫助的小夥伴,麻煩給這篇文章點點贊。如果大佬發現本文理解存在問題,希望可以在下方評論區指點我改正。最後謝謝大家的閱讀!小名祝大家每天都開開心心噠~😁