Dify Chat API 代理层实现原理:如何安全调用 Dify API 而不暴露密钥的完整方案
【免费下载链接】dify-app-hub一个 Dify 应用管理平台,基于 Dify API 构建,提供深度优化的用户端交互界面,支持 Chatflow、Workflow 等多种 Dify 应用类型,适配深度思考、思维链、图表渲染、文件处理等丰富的 AI 输出形式,提供开箱即用的 AI 应用解决方案。项目地址: https://gitcode.com/gh_mirrors/di/dify-app-hub
如果你曾在前端直接接入Dify Chat API,一定踩过一个坑:Dify API Key 一旦写进浏览器端代码,任何人打开 DevTools 或反编译 JS 就能拿到密钥。开源项目dify-app-hub(一个 Dify 应用管理平台)通过一层Dify API 代理层彻底解决了这个问题:前端只与平台自身的接口通信,密钥保存在服务端数据库,请求由服务端注入密钥后转发给 Dify。本文带你拆解这个代理层的完整实现,帮你快速掌握安全调用 Dify API 而不暴露密钥的工程化方案。
为什么需要 Dify API 代理层
把 Dify API Key 放在客户端有三个典型风险:
- 🔓密钥泄露:API Key 会出现在 JS 打包产物、请求头和网络面板中,等于公开;
- 🌐跨域限制:Dify 服务端对来源有访问控制,浏览器直连容易受 CORS 约束;
- 🗂️多应用难管理:Chatflow、Workflow 等多种应用类型各自一个 Key,分散在前端配置中无法统一管控。
Dify 官方在 API 文档中也明确提示:强烈建议将 API Key 放在后端服务中,而非直接放在客户端程序里。下图是 Dify 访问 API 页面中获取基础 URL 与 Key 安全提示的位置:
代理层正是针对这些痛点设计的"中间人":浏览器 → 平台代理接口 → Dify API,密钥全程不出服务端。
代理层总体架构:三层分离
dify-app-hub 的调用链路非常清晰,分三层:
| 层级 | 位置 | 职责 |
|---|---|---|
| 前端封装层 | web/lib/dify-client.ts | 构造DifyApi实例,只请求本地代理路径 |
| 服务端代理层 | web/app/api/client/dify/[appId]/ | 查库取密钥、注入鉴权头、透传流式响应 |
| 数据存储层 | web/db/schema/apps.ts | 在dify_apps表中持久化每个应用的 API Base 与 API Key |
请求方向示意:
浏览器 DifyApi 实例 │ POST /api/client/dify/{appId}/chat-messages ▼ Next.js 服务端代理(getAppItem 查库 → 注入 Bearer 密钥) │ Authorization: Bearer {apiKey} ▼ Dify API {apiBase}/chat-messages代理层目录web/app/api/client/dify/[appId]/下按 Dify 官方接口一一映射了chat-messages、completion-messages、conversations、files/upload、workflows/run等全部路由,前端无需感知具体端点差异。
密钥入库:在管理端配置 Dify 应用
每个 Dify 应用的API Base与API Key都存储在数据库dify_apps表中(见 db/schema/apps.ts,字段为api_base与api_key)。
在管理端"添加 Dify 应用"时只需填写两项配置,填写的正是上图 Dify 文档中获取的基础 URL 和密钥:
密钥的写入与读取都由服务端完成。web/repository/app.ts中的getAppItem函数按应用 ID 从数据库取出完整配置(含密钥),仅供代理路由内部使用,绝不直接返回给浏览器:
前端无密钥调用:DifyApi 封装层
前端的封装类DifyApi(lib/dify-client.ts)是理解代理层的关键。它构造请求时使用的基地址是平台自己的接口,而不是 Dify 的地址:
const PLATFORM_API_BASE = '/api/client/dify' const genXRequestOptions = (options: IDifyApiOptions) => ({ baseURL: `${PLATFORM_API_BASE}/${options.appId}`, headers: { 'x-user-id': LocalStorageStore.get('USER_ID') }, })几个值得注意的设计:
- 无密钥请求:
sendMessage、uploadFile、runWorkflow等方法都只请求/api/client/dify/{appId}/...,请求体中没有任何鉴权信息; - 用户身份头:通过
x-user-id请求头把当前用户身份传给代理层,用于 Dify 侧的会话隔离; - 切换应用即切换代理目标:
updateOptions更新appId后,后续请求自动指向另一个应用的代理路径。
也就是说,前端把"Dify 调用"完全抽象成了"平台调用",浏览器里根本不存在密钥。
服务端代理注入密钥:Bearer 鉴权代理
以流式聊天接口web/app/api/client/dify/[appId]/chat-messages/route.ts为例,代理逻辑四步走:
- 解析参数:从 URL 路径取出
appId; - 查库取密钥:调用
getAppItem(appId)拿到该应用的apiBase和apiKey,查不到返回 404; - 注入密钥转发:服务端发起
fetch请求到${apiBase}/chat-messages,并在请求头中拼入Authorization: Bearer ${apiKey}; - 错误透传:Dify 返回非 2xx 时原样透传状态码与错误体。
const response = await fetch(`${app.requestConfig.apiBase}/chat-messages`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${app.requestConfig.apiKey}`, }, body: JSON.stringify(data), })对于简单的 JSON 接口,代理层还抽出了通用函数proxyDifyRequest(lib/api-utils.ts),统一负责拼 URL、注入 Bearer 头,各路由只需传apiBase、apiKey和端点路径即可,避免每个路由重复写鉴权逻辑。
流式响应透传:SSE 代理的关键实现
Chat 场景下response_mode: streaming返回的是 SSE 事件流,代理层必须做到边收边发,否则前端要等整个响应结束才能看到首字。实现方式是新建一个ReadableStream,用reader.read()循环泵送数据:
const stream = new ReadableStream({ async start(controller) { const reader = response.body?.getReader() while (true) { const { done, value } = await reader.read() if (done) break controller.enqueue(value) // 收到一块立即转发一块 } controller.close() }, }) return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }, })同样的泵送模式也被用于 HITL 场景下重连工作流事件的workflow/[taskId]/events路由(web/app/api/client/dify/[appId]/workflow/[taskId]/events/route.ts),保证了工作流长任务的 SSE 事件也能实时透传。
密钥如何对前端隐身:敏感字段脱敏
代理层除了"藏"住密钥,还在应用管理接口上做了"脱敏"。lib/api-utils.ts中的createSafeApp在把应用信息返回给前端前,将密钥替换为占位符:
export function createSafeApp(app: IDifyAppItem) { return { ...app, requestConfig: { apiBase: app.requestConfig.apiBase, apiKey: '******', // 隐藏 API Key }, } }也就是说,即使是管理端查看应用详情的接口,前端拿到的apiKey也永远是******。真正可用的密钥只存在于数据库和服务端内存中,形成"入库存储 → 服务端读取 → 请求注入 → 出参脱敏"的完整闭环。
方案要点总结
用一张清单回顾这套 Dify API 代理层的设计:
- ✅ 密钥只存数据库(
dify_apps表),前端代码零密钥; - ✅ 前端
DifyApi只请求平台代理路径,通过x-user-id头传递用户身份; - ✅ 服务端路由查库后注入
Authorization: Bearer头转发请求; - ✅ SSE 流式响应通过
ReadableStream逐块透传,首字延迟不受影响; - ✅ 应用信息出参统一脱敏,密钥永不回传浏览器;
- ✅ 通用函数
proxyDifyRequest收敛鉴权逻辑,新增 Dify 接口只需加一条路由。
这套"代理层 + 数据库密钥 + 流式透传 + 出参脱敏"的组合,正是所有需要安全调用 Dify API 而不暴露密钥的场景可直接复用的完整方案。
【免费下载链接】dify-app-hub一个 Dify 应用管理平台,基于 Dify API 构建,提供深度优化的用户端交互界面,支持 Chatflow、Workflow 等多种 Dify 应用类型,适配深度思考、思维链、图表渲染、文件处理等丰富的 AI 输出形式,提供开箱即用的 AI 应用解决方案。项目地址: https://gitcode.com/gh_mirrors/di/dify-app-hub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考