社群開發者:LeoJie
GitHub :CCweixiao
座右銘:心之所向,素履以往
- 1. 背景
- 2. SSO 登入流程概述
- 3. SSO 接入流程細節
- 3.1 實作接口 SSOInterceptor
- 3.2 前端代碼的一些配合修改
- 3.3 SSO 登出
- 3.4 SSO 登入後 Cookie 丢失問題
- 4. 感覺可以繼續優化的地方
Linkis 的管理台和 DSS 目前支援以下使用者登入體系:
- 接入 LDAP 登入
- 接入 SSO 單點登入
- token 登入方式
- 代理使用者模式
除 SSO 單點登入之外,其他三種登入的接入方式都很容易實作,可以參考 DSS 的 wiki 文檔:
https://github.com/WeBankFinTech/DataSphereStudio/wiki/Login
SSO 登入的接入方式雖然也有文檔描述,但還不夠細節,同時我使用的 linkis 和 dss 的版本分别為1.0.3和dev-1.0.1,SSO 相關功能的代碼還存在些許瑕疵,不仔細測試的話,估計會遇到一些問題,而且,對于剛剛接觸的夥伴,這塊的代碼其實不好在本地 DEBUG,代碼也較為複雜,需要前後端配合進行測試。
鑒于此,為大家分享一下我的實作過程,以供參考。
SSO 的概念以及 SSO 接入的必要性在此不再贅述,網上有很多資料可以參考。雖然每家公司 SSO 的接入方式,在實作細節上或有所差異,但原理或思路應該大緻相同。以我們公司的 SSO 登入流程為例:
- 使用者通路 DSS&Linkis 中受保護的 api,Gateway 中的 SecurityFilter 會從 Cookie 緩存中比對使用者名,第一次登入或 Cookie 失效被清理時,SecurityFilter 擷取到空的使用者名後就會觸發 SSO 的登入邏輯,首先,背景标記此次請求的響應狀态為 401,同時,背景會把 SSO 的配置資訊(enableSSO、SSOURL 以及 SSOLogoutURL)傳回給前端。SSOLogoutURL 是我增加的一個傳回值,前端拿到 SSO 的登出 URL 之後,跳轉到該位址,就可以實作使用者登入登出的操作。
- 前端響應攔截器捕獲到 401 的響應狀态,同時擷取到 SSO 的配置資訊後,浏覽器會跳轉到 SSO 掃碼登入頁。
- 使用者在掃碼頁成功掃碼或登入後,SSO 伺服器會産生一條 token 記錄,該 token 字元串會作為一個 query param 參數拼接到回跳位址(DSS&Linkis 首頁)的後面,然後回跳。
- DSS&Linkis 的前端 router 解析路由位址上攜帶的 token 參數,把解析到的 token 字元串存儲到浏覽器的緩存中,後續,全局 request 從緩存中拿到 token,放到 request 的 header 中(或 cookie),再次請求 DSS&Linkis 的背景
- DSS&Linkis 背景解析 request header 中的 token 參數,請求 SSO 服務,校驗 token 的有效性,去拿使用者的資訊,成功擷取到使用者資訊後,生成 Cookie,存儲在背景緩存并傳給前端,前端緩存該 Cookie,便于下次請求時攜帶(浏覽器關閉後該 Cookie 就失效啦,由 cookie 的 max-age 屬性決定)
InternalSSOInterceptor
重點實作的幾個方法,每家公司的 SSO 邏輯都不一樣,是以,此處隻說明每個接口方法的作用。
/**
* 如果打開SSO單點登入功能,目前端跳轉SSO登入頁面登入成功後,前端再次轉發請求給gateway。
* 使用者需實作該接口,通過Request傳回user
* 解析token,請求SSO服務,校驗token有效性,擷取使用者資訊
* @param gatewayContext
* @return
*/
def getUser(gatewayContext: GatewayContext): String
/**
* 通過前端的requestUrl,使用者傳回一個可跳轉的SSO登入頁面URL。
* 要求:需帶上原請求URL,以便登入成功後能跳轉回來
* 生成回跳位址,傳回給前端,跳轉操作是由前端完成的
* @param gatewayContext
* @return
*/
def redirectTo(gatewayContext: GatewayContext): String
/**
* gateway退出時,會調用此接口,以保證gateway清除cookie後,SSO單點登入也會把登入資訊清除掉
* 清除cookie資訊,通路SSO服務的登出接口,使原有token失效,然後前端重新跳轉到SSO掃碼頁面
* @param gatewayContext
*/
def logout(gatewayContext: GatewayContext): Unit
/**
* 内部SSO 實作時需要一個登出的跳轉位址,前端拿到後跳轉到該位址上就可以實作SSO token的登出,(此方法是根據自己的情況做的擴充)
* @param gatewayContext
* @return
*/
def logoutRedirectTo(gatewayContext: GatewayContext): String
以下對前端代碼的一些調整,僅針對此版本 SSO 內建過程中遇到的一些問題的解決,後續版本可能對該功能有所優化,包含代碼層面。
api.js
api-sso
API 調用時需要在此處捕獲下 401,然後擷取 SSO 的配置資訊後放入浏覽器緩存中。
router.js
router-js
路由跳轉時,解析路由位址中的 param 參數——token,然後把 token 字元串放到浏覽器緩存中。此處還用到util.js中的一個工具方法:
/**
* 擷取路由位址中的參數
* @param name
* @returns {string|null}
*/
getUrlParam(name) {
const reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)')
const r = window.location.search.substr(1).match(reg)
if (r != null) return unescape(r[2])
return null // 傳回參數值
},
token 緩存之後,再回到api.js的全局 request 處,要為每一個 request 增加一個名為'x-token'的 header。
api-header
這樣每次請求都會攜帶 token 傳給後端,(此處也可以考慮用 cookie 存儲,避免 token 字元串直接參與網絡傳輸)
登出的邏輯就是清除浏覽器緩存的 token,跳轉 SSO 的登出 URL,然後同時,後端會執行登出邏輯,并清除後端辨別使用者登入狀态的 Cookie 記錄。
其實,Linkis 0.11 版本修複了這個問題。詳見https://github.com/apache/incubator-linkis/issues/489
在 linkis-1.0.3 中這個 BUG 又出來了,可能是代碼的合并問題,此異常具體的表現是:
SSO 登入成功,但接口依舊報未登入異常,
401
原因是,浏覽器 Cookie 緩存中沒有使用者的登入資訊,導緻,後端會持續進行 SSO 的登入操作。
cookie
修改代碼後問題解決:
resolve
後端使用者登入成功後,登入狀态的 Cookie 存儲時設定的 maxAge 為-1,即浏覽器關閉後 Cookie 就被清除,其實可以設定 Cookie 的存活時間久一點,因為 SSO 的 token 的存活周期本身就比較長。
前端擷取到 token 後,存儲在 Cookie 中,而不是放在請求頭中與後端進行互動。
前端代碼
storage.set('x-token', token, 'cookie', 7 * 24 * 3600)
// 全局Request那裡就不需要把token放到每一個request請求的頭部啦
後端 token 擷取
public String getUser(GatewayContext gatewayContext) {
List<Cookie> cookieList = gatewayContext.getRequest().getCookies().entrySet().stream()
.flatMap(x-> Arrays.stream(x.getValue()))
.filter(c-> "x-token".equals(c.getName()))
.collect(Collectors.toList());
Cookie cookie = null;
if (!cookieList.isEmpty()) {
cookie = cookieList.get(0);
LOG.info("SSO_COOKIE_NAME {}", cookie.getName());
LOG.info("SSO_COOKIE_VALUE {}", cookie.getValue());
LOG.info("SSO_COOKIE_MAX_AGE {}", cookie.getMaxAge());
LOG.info(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
if (cookie == null) {
return "";
}
String token = cookie.getValue();
if (StringUtils.isBlank(token)) {
return "";
}
... ...
}
SSO 和 LDAP 雙登入,有些特殊賬戶,如管理者賬号和部門共享賬号,不能掃碼進行登入,也不好在 SSO 賬戶體系中加時,可以配合 LDAP 實作雙登入。