如何将 BI 工具与业务系统进行单点登录对接,实现用户权限通用
首先来看下两套系统的用户体系功能,左边是 BI 工具,右边是业务系统,需要实现用户权限对接和打通:
单点登录体系及用户场景
• 场景 1. 用户登录 Wyn BI 页面使用第三方业务系统账号• 场景 2. 用户使用第三方账号登录 wyn BI 以后需要获取用户信息(包括组织机构、用户上下文)• 场景 3. 用户在第三方页面调用 wyn BI 登录接口,获取 wyn BI 的登录 token
Wyn BI 安全提供程序接口
ISecurityProvider
• GenerateTokenAsync 生成用户 token 的核心方法(也是校验用户的核心方法)• ValidateTokenAsync 校验用户 token• DisposeTokenAsync 注销用户 token• GetUserRolesAsync 获取用户权限• GetUserOrganizationsAsync 获取用户组织机构• GetUserDescriptorAsync 获取用户描述• GetUserContextAsync 获取用户上下文
ISecurityProviderFactory
• CreateAsync 根据外部配置生成 ISecurityProvider
IExternalUserContext
• GetValueAsync 获取用户上下文单值属性• GetValuesAsync 获取用户上下文多值属性
IExternalUserDescriptor
• ExternalUserId 用户 ID• ExternalUserName 用户名称• ExternalProvider
可以看到 Wyn 提供的这几个接口,指的就是 Wyn 的用户权限控制模块。
前置配置
当对接 Wyn 权限体系使用 数据库或 API 接口等方式时,往往希望能把关键接口地址 或者数据库配置信息能在前端显示修改, 这样能方便后续修改该配置而不用再修改代码。
关于配置项在前端往往显示为 key:value 形式, 表现为代码层面就是 ISecurityProviderFactory 工厂类的 SupportedSettings 属性,不难看出 SupportedSettings 类型便是 ConfigurationItem 的数组(可迭代)形式, 一般可把需要设置的 setting key 通过硬编码的方式赋给 SupportedSettings 对象,来完成配置项对接。
ISecurityProviderFactory 该工厂类的 CreateAsync 方法便是安全提供程序的初始化入口, 在这里可以将外部配置信息通过 ConfigurationItem 对象来注入安全提供程序中, 以便后续查询用户信息使用。
场景 1
由上图可以看出整个 Wyn 登录的接口入口函数就是 GenerateTokenAsync 函数来生成 token,该函数的参数就是用户登录输入的用户名称、密码 (其他参数,场景 3 细讲), 最后产生结果就是一条用户 token,这个 token 可以理解为用户在 wyn 中的当前用户信息索引。
从校验 token 信息之后的所有函数方法参数都是这条生成的 token,所以易知后面的获取用户上下文、用户信息描述、用户权限、用户组织机构. 它们的基本思路都是 token 索引-->获取用户信息-->由用户信息构建要获取的对象(上下文、组织机构......)--> 返回获取对象。
这里构建 token 的方法没有规定,所以可以使用多种方法来生成 token。
• 将用户信息通过编码加密方式直接存为 token,后续获取用户信息直接反向解密即可拿到• 将用户信息放到内存(redis)Map(dict)容器中,token 即为对应键值对的 key,后续通过 get(key) 的方式来获取用户信息• 将第三方查询该用户信息的关键参数如 userId, userName 等参数编码为 token, 后续通过解密为查询参数然后重新查询用户信息来获取
场景 2
显而易见,IExternalUserContext 实际上就是用户信息的访问器, 指定的访问器方法分别是 GetValueAsync 和 GetValuesAsync . 这两个分别对应单值参数和多值参数, 通常情况下, 都会在 UserContext 对象中内置一个 User 对象来实现上述两个访问器的实现逻辑。当然在构建 UserContext 时,用户对象就要建立好, 这个就不赘述了。
IExternalUserDescriptor 类似, 但是因为它的访问属性是固定的,包含了用户 id、用户名称等三个属性, 所以没必要再内置 user 对象, 直接构建 IExternalUserDescriptor 对应属性即可
场景 3
获取 token 接口
// curl 调用 curl --location --request POST 'http://localhost:51980/connect/token'
--header 'User-Agent: Apifox/1.0.0 (https://www.apifox.cn)'
--data-urlencode 'username=<username>'
--data-urlencode 'password=<password>'
--data-urlencode 'grant_type=<grant_type>'
--data-urlencode 'client_id=<client_id>'
--data-urlencode 'client_secret=<client_secret>'
--data-urlencode 'tenant_path=<tenant_path>'
--data-urlencode 'access-token-lifetime=<access-token-lifetime>'
//javascript fetch 代码
var myHeaders = new Headers();myHeaders.append("Content-type", "application/x-www-form-urlencoded");
var urlencoded = new URLSearchParams();urlencoded.append("username", "<username>");urlencoded.append("password", "<password>");urlencoded.append("grant_type", "<grant_type>");urlencoded.append("client_id", "<client_id>");urlencoded.append("client_secret", "<client_secret>");urlencoded.append("tenant_path", "<tenant_path>");urlencoded.append("access-token-lifetime", "<access-token-lifetime>");
var requestOptions = {method: 'POST',headers: myHeaders,body: urlencoded,redirect: 'follow'};
fetch("http://localhost:51980/connect/token", requestOptions).then(response => response.text()).then(result => console.log(result)).catch(error => console.log('error', error));
// axios 调用
var axios = require('axios');var qs = require('qs');var data = qs.stringify({'username': '<username>','password': '<password>','grant_type': '<grant_type>','client_id': '<client_id>','client_secret': '<client_secret>','tenant_path': '<tenant_path>','access-token-lifetime': '<access-token-lifetime>'});var config = {method: 'post',url: 'http://localhost:51980/connect/token',headers: {"Content-type" : "application/x-www-form-urlencoded"},data : data};
axios(config).then(function (response) {console.log(JSON.stringify(response.data));}).catch(function (error) {console.log(error);});
以上是 curl 和 javascript fetch/axios 调用获取 token 的方法, 接下来对参数内容加以说明:• username 用户名 必填• password 密码 必填• grant_type 定值 "password" 必填• client_id 客户端 id 访问 客户端管理 可查询 或 查询 帮助文档 必填• client_secret 客户端私钥 访问 客户端管理 可查询 或 查询 帮助文档 必填• tenant_path 组织机构角色参数,形如 /A2/C$3 表示 A 部门 1 角色下的 B 部门 2 角色下的 C 部门 3 角色 选填• access-token-lifetime token 过期时间,单位秒 选填这部分实际上只是第三方调用 Wyn 接口的场景,按场景 1,场景 2 的内容开发完理应可以直接适应场景 3 的, 但需要有几个额外注意的点:
tenant_path 这个参数表示的是组织机构角色参数, 也就是说这个场景下可能出现用户和 Wyn 中的组织机构通过这个参数来绑定, 所以在生成 token 的方法中也需要额外增加处理。tenant_path 参数并将其和用户信息进行绑定, 以便在后面的获取组织机构方法 GetUserOrganizationsAsync 中使用。
额外参数, 除了上面接口中的参数之外, 也可以添加其他自定义参数,以供生成 token 方法中使用,具体值可以从 customizedParam 中拿到。var externalParams = customizedParam as Dictionary<string, string>;string externalParam = externalParams["key"]
代码建议在 ISecurityProvider 方法中可能需要查询数据库、调用 API、调用 SDK 的方式来获取第三方的用户信息, 这里建议加一层抽象的 service 功能层供 ISecurityProvider 调用使用, 在 service 层下层在添加连接数据库或者调用 API 的基础查询层, 这一层内容与业务代码完全无关, 只专注于实现后台基础的查询功能。
版权声明: 本文为 InfoQ 作者【葡萄城技术团队】的原创文章。
原文链接:【http://xie.infoq.cn/article/4e9b8b81cfa8a75482c54a819】。文章转载请联系作者。
评论