![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIjBXPt9mcm9TMwIDMwcTY5EjZ4Q2M0ITY1QjM0ImMiRmZxUzMlVjY38CXwsWO0EHbyomdx1Sat42YtM3b09CXul2ZpJ3bvwVbvNmLn1WavFWa0V3b05iNyA3Lc9CX6MHc0RHaiojIsJye.jpg)
社区开发者: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 实现双登录。