本帖最后由 Bella.Yuan 于 2023-1-6 12:16 编辑
- 场景1. 用户登录wy页面使用第三方账号
- 场景2. 用户使用第三方账号登录wyn以后需要获取用户信息(包括组织机构、用户上下文)
- 场景3. 用户在第三方页面调用wyn登录接口,获取wyn的登录token
Wyn 安全提供程序接口
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 组织机构角色参数,形如 /A$1/B$2/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 的基础查询层, 这一层内容与业务代码完全无关, 只专注于实现后台基础的查询功能
|