分享链接身份鉴权

FastGPT 分享链接身份鉴权

介绍

在 FastGPT V4.6.4 中,我们修改了分享链接的数据读取方式,为每个用户生成一个 localId,用于标识用户,从云端拉取对话记录。但是这种方式仅能保障用户在同一设备同一浏览器中使用,如果切换设备或者清空浏览器缓存则会丢失这些记录。这种方式存在一定的风险,因此我们仅允许用户拉取近30天20条记录。

分享链接身份鉴权设计的目的在于,将 FastGPT 的对话框快速、安全的接入到你现有的系统中,仅需 2 个接口即可实现。

使用说明

免登录链接配置中,你可以选择填写身份验证栏。这是一个POST请求的根地址。在填写该地址后,分享链接的初始化、开始对话以及对话结束都会向该地址的特定接口发送一条请求。下面以host来表示凭身份验证根地址。服务器接口仅需返回是否校验成功即可,不需要返回其他数据,格式如下:

接口统一响应格式

  1. {
  2. "success": true,
  3. "message": "错误提示",
  4. "msg": "同message, 错误提示",
  5. "data": {
  6. "uid": "用户唯一凭证"
  7. }
  8. }

FastGPT 将会判断success是否为true决定是允许用户继续操作。messagemsg是等同的,你可以选择返回其中一个,当success不为true时,将会提示这个错误。

uid是用户的唯一凭证,将会用于拉取对话记录以及保存对话记录。可参考下方实践案例。

触发流程

分享链接身份鉴权 - 图1

配置教程

1. 配置身份校验地址

分享链接身份鉴权 - 图2

配置校验地址后,在每次分享链接使用时,都会向对应的地址发起校验和上报请求。

🤖

这里仅需配置根地址,无需具体到完整请求路径。

2. 分享链接中增加额外 query

在分享链接的地址中,增加一个额外的参数: authToken。例如:

原始的链接:https://share.fastgpt.in/chat/share?shareId=648aaf5ae121349a16d62192

完整链接: https://share.fastgpt.in/chat/share?shareId=648aaf5ae121349a16d62192&authToken=userid12345

这个authToken通常是你系统生成的用户唯一凭证(Token之类的)。FastGPT 会在鉴权接口的body中携带 token={{authToken}} 的参数。

3. 编写聊天初始化校验接口

请求示例 鉴权成功 鉴权失败

  1. curl --location --request POST '{{host}}/shareAuth/init' \
  2. --header 'Content-Type: application/json' \
  3. --data-raw '{
  4. "token": "{{authToken}}"
  5. }'
  1. {
  2. "success": true,
  3. "data": {
  4. "uid": "用户唯一凭证"
  5. }
  6. }

系统会拉取该分享链接下,uid 为 username123 的对话记录。

  1. {
  2. "success": false,
  3. "message": "身份错误",
  4. }

4. 编写对话前校验接口

请求示例 鉴权成功 鉴权失败

  1. curl --location --request POST '{{host}}/shareAuth/start' \
  2. --header 'Content-Type: application/json' \
  3. --data-raw '{
  4. "token": "{{authToken}}",
  5. "question": "用户问题",
  6. }'
  1. {
  2. "success": true,
  3. "data": {
  4. "uid": "用户唯一凭证"
  5. }
  6. }
  1. {
  2. "success": false,
  3. "message": "身份验证失败",
  4. }
  1. {
  2. "success": false,
  3. "message": "存在违规词",
  4. }

5. 编写对话结果上报接口(可选)

该接口无规定返回值。

响应值与chat 接口格式相同,仅多了一个token

重点关注:totalPoints(总消耗AI积分),token(Token消耗总数)

  1. curl --location --request POST '{{host}}/shareAuth/finish' \
  2. --header 'Content-Type: application/json' \
  3. --data-raw '{
  4. "token": "{{authToken}}",
  5. "responseData": [
  6. {
  7. "moduleName": "core.module.template.Dataset search",
  8. "moduleType": "datasetSearchNode",
  9. "totalPoints": 1.5278,
  10. "query": "导演是谁\n《铃芽之旅》的导演是谁?\n这部电影的导演是谁?\n谁是《铃芽之旅》的导演?",
  11. "model": "Embedding-2(旧版,不推荐使用)",
  12. "tokens": 1524,
  13. "similarity": 0.83,
  14. "limit": 400,
  15. "searchMode": "embedding",
  16. "searchUsingReRank": false,
  17. "extensionModel": "FastAI-4k",
  18. "extensionResult": "《铃芽之旅》的导演是谁?\n这部电影的导演是谁?\n谁是《铃芽之旅》的导演?",
  19. "runningTime": 2.15
  20. },
  21. {
  22. "moduleName": "AI 对话",
  23. "moduleType": "chatNode",
  24. "totalPoints": 0.593,
  25. "model": "FastAI-4k",
  26. "tokens": 593,
  27. "query": "导演是谁",
  28. "maxToken": 2000,
  29. "quoteList": [
  30. {
  31. "id": "65bb346a53698398479a8854",
  32. "q": "导演是谁?",
  33. "a": "电影《铃芽之旅》的导演是新海诚。",
  34. "chunkIndex": 0,
  35. "datasetId": "65af9b947916ae0e47c834d2",
  36. "collectionId": "65bb345c53698398479a868f",
  37. "sourceName": "dataset - 2024-01-23T151114.198.csv",
  38. "sourceId": "65bb345b53698398479a868d",
  39. "score": [
  40. {
  41. "type": "embedding",
  42. "value": 0.9377183318138123,
  43. "index": 0
  44. },
  45. {
  46. "type": "rrf",
  47. "value": 0.06557377049180328,
  48. "index": 0
  49. }
  50. ]
  51. }
  52. ],
  53. "historyPreview": [
  54. {
  55. "obj": "Human",
  56. "value": "使用 <Data></Data> 标记中的内容作为你的知识:\n\n<Data>\n导演是谁?\n电影《铃芽之旅》的导演是新海诚。\n------\n电影《铃芽之旅》的编剧是谁?22\n新海诚是本片的编剧。\n------\n电影《铃芽之旅》的女主角是谁?\n电影的女主角是铃芽。\n------\n电影《铃芽之旅》的制作团队中有哪位著名人士?2\n川村元气是本片的制作团队成员之一。\n------\n你是谁?\n我是电影《铃芽之旅》助手\n------\n电影《铃芽之旅》男主角是谁?\n电影《铃芽之旅》男主角是宗像草太,由松村北斗配音。\n------\n电影《铃芽之旅》的作者新海诚写了一本小说,叫什么名字?\n小说名字叫《铃芽之旅》。\n------\n电影《铃芽之旅》的女主角是谁?\n电影《铃芽之旅》的女主角是岩户铃芽,由原菜乃华配音。\n------\n电影《铃芽之旅》的故事背景是什么?\n日本\n------\n谁担任电影《铃芽之旅》中岩户环的配音?\n深津绘里担任电影《铃芽之旅》中岩户环的配音。\n</Data>\n\n回答要求:\n- 如果你不清楚答案,你需要澄清。\n- 避免提及你是从 <Data></Data> 获取的知识。\n- 保持答案与 <Data></Data> 中描述的一致。\n- 使用 Markdown 语法优化回答格式。\n- 使用与问题相同的语言回答。\n\n问题:\"\"\"导演是谁\"\"\""
  57. },
  58. {
  59. "obj": "AI",
  60. "value": "电影《铃芽之旅》的导演是新海诚。"
  61. }
  62. ],
  63. "contextTotalLen": 2,
  64. "runningTime": 1.32
  65. }
  66. ]
  67. }'

responseData 完整字段说明:

  1. type ResponseType = {
  2. moduleType: FlowNodeTypeEnum; // 模块类型
  3. moduleName: string; // 模块名
  4. moduleLogo?: string; // logo
  5. runningTime?: number; // 运行时间
  6. query?: string; // 用户问题/检索词
  7. textOutput?: string; // 文本输出
  8. tokens?: number; // 上下文总Tokens
  9. model?: string; // 使用到的模型
  10. contextTotalLen?: number; // 上下文总长度
  11. totalPoints?: number; // 总消耗AI积分
  12. temperature?: number; // 温度
  13. maxToken?: number; // 模型的最大token
  14. quoteList?: SearchDataResponseItemType[]; // 引用列表
  15. historyPreview?: ChatItemType[]; // 上下文预览(历史记录会被裁剪)
  16. similarity?: number; // 最低相关度
  17. limit?: number; // 引用上限token
  18. searchMode?: `${DatasetSearchModeEnum}`; // 搜索模式
  19. searchUsingReRank?: boolean; // 是否使用rerank
  20. extensionModel?: string; // 问题扩展模型
  21. extensionResult?: string; // 问题扩展结果
  22. extensionTokens?: number; // 问题扩展总字符长度
  23. cqList?: ClassifyQuestionAgentItemType[]; // 分类问题列表
  24. cqResult?: string; // 分类问题结果
  25. extractDescription?: string; // 内容提取描述
  26. extractResult?: Record<string, any>; // 内容提取结果
  27. params?: Record<string, any>; // HTTP模块params
  28. body?: Record<string, any>; // HTTP模块body
  29. headers?: Record<string, any>; // HTTP模块headers
  30. httpResult?: Record<string, any>; // HTTP模块结果
  31. pluginOutput?: Record<string, any>; // 插件输出
  32. pluginDetail?: ChatHistoryItemResType[]; // 插件详情
  33. isElseResult?: boolean; // 判断器结果
  34. }

实践案例

我们以Laf作为服务器为例分享链接身份鉴权 - 图3,简单展示这 3 个接口的使用方式。

1. 创建3个Laf接口

分享链接身份鉴权 - 图4

/shareAuth/init /shareAuth/start /shareAuth/finish

这个接口中,我们设置了token必须等于fastgpt才能通过校验。(实际生产中不建议固定写死)

  1. import cloud from '@lafjs/cloud'
  2. export default async function (ctx: FunctionContext) {
  3. const { token } = ctx.body
  4. // 此处省略 token 解码过程
  5. if (token === 'fastgpt') {
  6. return { success: true, data: { uid: "user1" } }
  7. }
  8. return { success: false,message:"身份错误" }
  9. }

这个接口中,我们设置了token必须等于fastgpt才能通过校验。并且如果问题中包含了字,则会报错,用于模拟敏感校验。

  1. import cloud from '@lafjs/cloud'
  2. export default async function (ctx: FunctionContext) {
  3. const { token, question } = ctx.body
  4. // 此处省略 token 解码过程
  5. if (token !== 'fastgpt') {
  6. return { success: false, message: "身份错误" }
  7. }
  8. if(question.includes("你")){
  9. return { success: false, message: "内容不合规" }
  10. }
  11. return { success: true, data: { uid: "user1" } }
  12. }

结果上报接口可自行进行逻辑处理。

  1. import cloud from '@lafjs/cloud'
  2. export default async function (ctx: FunctionContext) {
  3. const { token, responseData } = ctx.body
  4. const total = responseData.reduce((sum,item) => sum + item.price,0)
  5. const amount = total / 100000
  6. // 省略数据库操作
  7. return { }
  8. }

2. 配置校验地址

我们随便复制3个地址中一个接口: https://d8dns0.laf.dev/shareAuth/finish, 去除/shareAuth/finish后填入身份校验:https://d8dns0.laf.dev

分享链接身份鉴权 - 图5

3. 修改分享链接参数

源分享链接:https://share.fastgpt.in/chat/share?shareId=64be36376a438af0311e599c

修改后:https://share.fastgpt.in/chat/share?shareId=64be36376a438af0311e599c&authToken=fastgpt

4. 测试效果

  1. 打开源链接或者authToken不等于fastgpt的链接会提示身份错误。
  2. 发送内容中包含你字,会提示内容不合规。

使用场景

这个鉴权方式通常是帮助你直接嵌入分享链接到你的应用中,在你的应用打开分享链接前,应做authToken的拼接后再打开。

除了对接已有系统的用户外,你还可以对接余额功能,通过结果上报接口扣除用户余额,通过对话前校验接口检查用户的余额。