近期有個WEB項目需要改造。業主找第三方搞了一個集成站點,將多個應用站點的鏈接集中放在一個導航頁面。由于進入集成站點時已經登錄過了,業主要求點擊這些應用站點的鏈接時就不必再登錄。
以前做過類似項目,用的是單點登錄。大家都用同一個登錄系統,一次登錄,到處同行,不亦快哉。不過也有一些缺點,一是單點登錄比較復雜,不好搞。之前我們用過一個開源的單點登錄系統cas,代碼一大堆,部署也很復雜,然后每個使用它的應用都要有個客戶端,總之非常復雜。出了問題也不知道是哪里的毛病。最常見的現象就是多次重定向,用戶登錄信息在客戶端和服務器之間無限被踢皮球。所以每次想到要用這個東東都心煩意亂,甚至嚇得面無人色。(詳見拙作:《21世紀應用開源單點登錄項目CAS之集大成者》)
這還不是最壞的。最大的問題是,使用單點登錄,勢必要維護共同的用戶信息,要么是所有系統都使用同一個用戶表,要么是大家同步用戶信息。不同的應用系統通常由不同的公司開發,每個都有自己的設計,現在要集成在一起,改造工作量可想而知。有些年代久遠,早就過維護期,想改造都不可能。
一、自動登錄方案
但這次沒有使用單點登錄。第三方公司給出了一個方案:
1)在集成站點的導航頁面點擊應用站點鏈接時,系統分配一個token;
2)應用站點可以訪問集成站點的接口對token進行驗證,并獲取對應的用戶信息。這些用戶信息是集成站點的,應用系統不一定有;
3)token驗證通過后,應用站點就可以自己決定是否讓他登錄本系統了。
這種思想,有點類似auth。auth是第三方驗證通過后就自動放行了,而這里的方案是,接下來還要應用系統自己做一些處理,即如何在本系統里放行。
這個方案的好處是,不一定要擁有相同的用戶信息。如果要求不嚴格,應用系統驗證帶過來的token后就可以用一個默認的賬號自動登錄;如果非要是同一個賬號,那么因為驗證token的時候會返回用戶信息,應用系統完全可以之同步到自己的庫里。所以這個方案比較靈活,應用系統的主動權較大,修改工作量比較小,難度也較小。人家有高手啊。
二、應用系統的實現
我們系統采用java開發,安全框架是Spring Security。我的思路是:
1)訪問本應用系統時,檢查有無帶上第三方token,有則執行第2步,無則轉向本系統的登錄頁面
2)驗證第三方token合法,則系統自動登錄,否則轉向本系統的登錄頁面
關鍵是如何自動登錄。
我從前端的登錄頁面,按圖索驥,發現登錄按鈕點擊后,會提交到后端的“/auth/token”。但是我找來找去,都找不到對應的代碼。后端用的這個框架我不熟悉,一問才知,/auth/token
是Spring Security自己的實現。這個接口訪問后,會返回一個json對象,里面有個關鍵元素,叫“access_token”,我們前端就是憑這個來認定是否已經登錄了本系統的。
很自然地,我要在系統里實現自動登錄,那我應該創建并返回這個access_token給前端。問題是,這個創建過程是黑箱,我搞來搞去,都生成不了類似的令牌。最后放棄了,何必自己去搞,系統模擬前端提交,訪問一下自己這個/auth/token
不就好了嗎?代碼如下:
@GetMapping(value = "/autoLogin")public String autoLogin(@RequestParam(value="token3") String token3) {return autoLoginService.autoLogin(token3);}
@Overridepublic String autoLogin(String token3) {//token3,第三方tokenString re = null;if(checkToken3(token3)) {re = login();} else {re = "token未經授權";}return re;}private boolean checkToken3(String token) {boolean ok = false;//checkUrl,驗證第三方token網址String url = String.format("%s?token=%s", checkUrl,token);try {String re = HttpUtils.get(url);JSONObject jobj = JSONObject.parseObject(re);ok = jobj.get("code").toString().equals("200");} catch (Exception ex) {System.err.println(ex.getMessage());}return ok;}private String login() {String re = null;//參照前端提交的參數Map<String, String> params = new HashMap<>();params.put("tenantCode", "10001");params.put("username", account);params.put("password", password);params.put("type", "account");params.put("scope", "ui");params.put("grant_type", "password");params.put("client_id", "browser");Map<String, String> heads = new HashMap<>();heads.put("Authorization", "巴拉巴拉巴拉");heads.put("Content-Type", "application/x-www-form-urlencoded");try {//提交給自己的接口,登錄并返回access_token等。HttpUtils是自己寫的靜態類re = HttpUtils.post(String.format("http://localhost:%s/api/uaa/oauth/token", port), params, heads);} catch (Exception e) {re = e.getMessage();}return re;}