Dify 自架實戰 · 情境式手冊

Dify 情境式使用手冊:工具 / 知識庫 / 代理 / MCP 到底怎麼配

Dify 把能力切得很開——知識庫、工具、代理、應用各自獨立。概念很強,但沒有一份「我想做 X 就照著點」的手冊,連老手都會卡。這頁用情境式(case study)寫:每個情境給你「想達成什麼 → 官方步驟手順 → 截圖 → 會卡在哪」。照真實自架環境(Dify 1.14)整理。

🧭 本頁導覽
PART 一 · 先建心智模型
Dify 的四塊積木(知識庫 / 工具 / 代理 / 應用)
PART 二 · 情境式 how-to(一步步串)
情境 1|agent 上網查最新資料 情境 2|agent 查我自己的文件(RAG) 情境 3|agent 呼叫我的系統 / API 情境 4|用別人架好的 MCP(client) 情境 5|把我的 app 開成 MCP(server) 情境 6|資料該用 RAG / 工具 / MCP?決策圖 🏅 黃金組合:Chat + RAG → 開成 MCP
PART 三 · 選型速查 + 這版踩到的坑
選型速查表 這版實測踩到的坑 / 限制
PART 四 · 企業落地:授權架構(AAD / SSO)
授權架構總覽(gateway / n8n / PEP·PDP / 身分 / n8n sample)
PART 一 · 先建心智模型

0. 先建心智模型:Dify 的四塊積木

搞懂這四塊各自是什麼、怎麼組合,後面所有情境都變簡單。

flowchart LR
  subgraph APP["🖥️ 應用(App)= 對外的成品"]
    direction TB
    CHAT["聊天助手"]:::a
    AGENT["Agent"]:::a
    CF["Chatflow"]:::a
    WF["Workflow"]:::a
  end
  MODEL["🧠 模型
雲端 / 本機"]:::m KB["📚 知識庫 (RAG)
你的靜態文件"]:::k TOOL["🔧 工具
內建 / 自訂API / MCP"]:::t MODEL --> APP KB -->|"當上下文"| APP TOOL -->|"agent 可呼叫"| APP APP -.->|"發佈成 MCP server"| OUT["🌐 別的 agent 來用"]:::o classDef a fill:#dbeafe,stroke:#2563eb,color:#17335e classDef m fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d classDef t fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef o fill:#fef3c7,stroke:#d97706,color:#78350f
積木是什麼在 Dify 哪裡
🧠 模型agent 的腦。雲端(Groq/OpenAI…)或本機(Ollama)設定 → 模型供應商
📚 知識庫 (RAG)你上傳的靜態文件,給 app 當「上下文」查頂上「知識庫」
🔧 工具agent 能呼叫的能力:內建、自訂(你的 API)、MCP(別人的)頂上「工具」(內建 / 自定義 / 工作流 / MCP 四個分頁)
🖥️ 應用 (App)把上面組起來的成品:聊天 / Agent / Chatflow / Workflow頂上「工作室」
一句話:知識庫是「給它查的靜態資料」;工具是「讓它去做的動作 / 查活系統」;應用是「把腦 + 知識 + 工具組成的成品」;MCP 則是應用與工具「跨平台互通」的協定。
PART 二 · 情境式 how-to(一步步串)

情境 1|我要 agent 會上網查最新資料

🎯 想達成:agent 回答時能查即時的公開網路,而不是只靠模型舊知識。
  1. 頂上 工具 → 內建,確認有 DuckDuckGo / WebScraper 之類搜尋工具。
  2. 回你的 Agent app → 提示詞下方「工具」區 → + 加入 → 選內建搜尋工具。
  3. 提示詞寫行為(「需要最新資料時上網搜尋,並附上來源網址」),不要寫死工具名。
  4. 問一題只有網路上才有的問題,驗證它有呼叫、且附得出真連結。
卡點:提示詞寫「使用 XXX 工具」常失效(名字對不上);改寫「行為」讓模型自己配對。要求「附來源網址」是最好的幻覺偵測器——真查才貼得出真連結。

情境 2|我要 agent 查「我自己的文件」(RAG)

🎯 想達成:agent 照你上傳的 PDF / Word / Excel 回答,還附得出出處。
  1. 頂上 知識庫 → 建立 → 上傳文件(平台自動切塊 + 算向量)。embedding 要選本機 bge-m3 之類(很多雲端只有聊天模型、沒有 embedding)。
  2. 建好先做 命中測試(RAG 鐵律:先單獨驗檢索撈對了沒,再接 agent)。
  3. 回 app → 「上下文」區 → 掛上這個知識庫。
  4. (Chatflow)把 LLM 節點的「上下文」欄位接到 知識檢索的 result —— 這條線最容易漏,漏了就「查不到」或亂編。
卡點:知識庫適合靜態、不常變的文件;會變動的即時資料(訂單、庫存)不要塞 RAG,那要用「工具」(見情境 3)。機密文件則要 embedding + 聊天模型兩個都本機

➡️ 深入:Dify RAG 雙庫架構

情境 3|我要 agent 呼叫「我的系統 / API / 資料庫」

🎯 想達成:agent 能查你會變動的活系統(訂單狀態、庫存、ERP),甚至做動作。
  1. 頂上 工具 → 自定義 → 建立自定義工具
  2. 貼一份 OpenAPI schema,只講四件事:系統網址、有什麼動作、要什麼參數、這動作幹嘛(描述要清楚,agent 靠它判斷該不該用)。
  3. 授權方式:真系統通常選 API Key → Bearer,貼你的金鑰(找系統的「不過期整合金鑰」最省事)。
  4. 把工具掛到 Agent app,問一題,去後端查紀錄驗證它真的打了你的系統。
Dify 建立自定義工具:貼 OpenAPI schema 的畫面
工具 → 自定義 → 建立自定義工具:貼 OpenAPI schema
Dify 解析出 query_order 工具與 id 參數
貼完 Dify 自動解析出工具與參數(這裡是示範用的訂單查詢)
兩個一定撞的坑:SSRF:Dify 預設封鎖內網 IP,你的系統在內網會被擋 → 放行信任位址,或走「有效憑證的公開網域(443)」。②認證過期:貼死的登入 token 會斷 → 用不過期金鑰。

➡️ 深入:自訂工具 / MCP 架構

情境 4|我要 agent 用「別人架好的 MCP」

🎯 想達成:借用別人(或你自己另外架)的 MCP server 提供的工具。這是你當「使用方 / client」。
  1. 頂上 工具 → MCP 分頁新增 MCP 伺服器 (HTTP)
  2. 填對方的 伺服器 URL、命名、識別碼;需要的話設身份驗證。
  3. 存好後,這台 MCP 的工具就會出現在工具清單,可以掛到你的 Agent app。
Dify 工具 MCP 分頁:新增 MCP 伺服器 HTTP
工具 → MCP → 新增 MCP 伺服器(這是「接別人的」= client 側)
認清方向:這個 MCP 分頁是接別人的(你去用人家的工具),不是把你的 app 發佈出去。發佈出去看情境 5。

情境 5|我要把「我的東西」開放給別的 agent 當工具(我當 MCP server)

🎯 想達成:把你的 Dify app 變成「別的 agent(Claude Desktop / Cursor / 其他平台)能呼叫的工具」。這是你當「提供方 / server」。
實測先講結論:這版 Dify 的「發佈」下拉、app「概覽」頁,任何 app 型別都沒有原生 MCP 發佈開關。官方文件說 v1.6.0+ 有原生 toggle,但這個 build 沒把它接進可見位置。UI 正解是裝外掛
  1. 右上 外掛探索 Marketplace → 搜 mcp-server(說明「make dify's workflow as a MCP server」)→ 安裝
  2. 外掛 → 點該外掛 → 端點(Endpoints)新增端點
  3. 填四格:App(要開放的 app)、App TypeApp Input Schema(見下)、Auth Bearer Token(當密碼)。
  4. 存檔 → 得到端點網址 /e/<id>/mcp(還有 /sse)。
關鍵限制(實測):App Type 只有 Chat / Workflow,Agent 型開不了硬選 Agent 會回 Agent Chat App does not support blocking mode——Agent 是多步 ReAct、只能串流,而外掛用 blocking 呼叫。想對外開 MCP,就把邏輯做成 Chatflow(能掛工具、能忠實呈現、又支援 blocking)。

App Input Schema 要填一整個「MCP 工具定義」JSON(name / description / inputSchema),不是只填參數:

{
  "name": "query_order",
  "description": "查詢訂單狀態,回傳原文",
  "inputSchema": {
    "type": "object",
    "properties": { "query": { "type": "string", "description": "使用者問題" } },
    "required": ["query"]
  }
}
顯示 localhost 是正常的:那只是 Dify base URL 沒設,把主機換成你的公開網域(如 https://你的網域/e/<id>/mcp)就對外通。實測跨機器成功:另一台機器上的 AI 用 MCP client 連進來 → initialize → tools/list → tools/call → 跑你的 Chatflow → 拿到結果。「別的 AI 把你的 Dify 當工具」整條跑通。
Dify 發佈下拉沒有 MCP 選項
佐證:app 的「發佈」下拉沒有 MCP;概覽頁也沒有 → 要走外掛
MCP 協定版本(實測 + 2026 更新):MCP 用日期版本號(YYYY-MM-DD)。這台 Dify(外掛)談成的是 2024-11-05——最初 launch 那一版、最舊的。目前正式最新是 2025-11-25,另有 2026-07-28 RC(launch 以來最大改版,核心改成 stateless)。好消息:tools/list / tools/call 向後相容,基本串接照跑;但新版功能(結構化輸出、OAuth 流程、stateless)這台還沒有 → 要新特性得等 Dify/外掛升級,或改用跟得較前的 MCP server(n8n / FastMCP)。
資安鐵律:MCP 端點網址內含認證,等於 API key——別放進任何公開內容;真實/敏感資料場景,token 要用強隨機字串、用完把端點停用(端點卡片有啟用開關)。
又一次,Chatflow 是對的載體:它同時解了「忠實呈現不幻覺」+「能對外開成 MCP」。要更高彈性(細粒度多工具、自訂 schema)則走自己寫一台 MCP server(如 FastMCP,或用 n8n 的 MCP Server Trigger)。

情境 6|我要用 Agent 問,但後面是 API / RAG / 一張表 / 一份文件——該怎麼接?

這是最常卡的:同樣一份「資料」,到底該做成知識庫自訂工具、還是MCP?先看資料的性質,不是看它「是什麼檔」。

flowchart TD
  Q{"這份資料的性質?"}:::q
  Q -->|"靜態文件 / 知識 / 問答
(PDF、手冊、規章)"| RAG["📚 做成知識庫 (RAG)
掛到 app 上下文"]:::k Q -->|"會變動 / 要即時查 / 能做動作
(訂單、庫存、DB)"| API{"有現成 API 嗎?"}:::d API -->|"有"| CT["🔧 自訂工具
貼 OpenAPI schema"]:::t API -->|"沒有,但想開放/跨平台"| MCP["🌐 自己寫 MCP server
包成工具"]:::o Q -->|"就幾筆 / 一張小表"| SMALL["兩種都行:
塞知識庫最快 / 做小 API 較活"]:::s classDef q fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef d fill:#dbeafe,stroke:#2563eb,color:#17335e classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d classDef t fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef o fill:#fef3c7,stroke:#d97706,color:#78350f classDef s fill:#f1f5f9,stroke:#64748b,color:#334155
你手上的東西建議做法為什麼
一份文件 / 一本手冊知識庫 (RAG)靜態、要語意查、附出處
一個資料庫 / ERP(有 API)自訂工具(OpenAPI)即時、會變、走現成介面
一個資料庫(沒 API)先包一層 API,再做自訂工具 / MCP不要讓 agent 直連 DB,走乾淨介面
一份 RAG,想開放給別的 agent用 chat/agent app 接知識庫 → 發佈成 MCP;或自己寫 MCP server對外變成「問就有答」的工具
就 10 筆資料 / 一張小表塞知識庫最快;要常變就做小 API看會不會變
那「我要用 Agent 問、需要一個 MCP」怎麼串起來?順序是:①先決定後面那份資料用 RAG 還是工具(上表)→ ②做成一個 app(接知識庫或掛工具)→ ③把 app 發佈成 MCP(情境 5)→ ④外部 agent 連進來,問一句就拿到答案。MCP 只是「最外層的殼」,裡面包什麼(API/RAG/表/文件)由前三步決定。

🏅 黃金組合:Chat + RAG → 開成 MCP(最有價值的一條)

🎯 想達成:讓別的 AI 直接問「你的文件庫」,拿到有據答案——把「Chat + RAG」開成 MCP。

這是把 情境 2(RAG) 接上 情境 5(開 MCP),而且剛好全程用 Chat 型別——所以開得成 MCP(Agent 開不了)。完整六步:

  1. 建一個 基礎 Chat app(聊天助手)
  2. 提示詞:角色 + 規則(只根據上下文回答、禁止編造)。
  3. 上下文區 → 加入知識庫(RAG)(你的文件)。這一步就是 RAG,不需要 Agent。
  4. 測一題只在文件裡的問題,確認有據回答 + 附出處
  5. 發佈(發布更新)。
  6. 外掛 → 裝 mcp-server → 新增端點:App 選這個 Chat app、App Type = Chat、App Input Schema 填工具定義 JSON、設 Bearer Token → 拿到 MCP 網址。
成果:把 MCP 網址(換成公開網域)交給別的 AI(Claude / Cursor…),它就能問你的文件庫、拿到有據答案。關鍵:全程 Chat 型別 → 開得成 MCP;千萬別用 Agent(開不了 blocking)。
安全:機密文件的知識庫別開成 MCP(會被外部拉走);要開就用乾淨資料,或端點設強 token + 用完停用。
PART 三 · 選型速查 + 這版踩到的坑

選型速查表

知識庫 vs 自訂工具 vs MCP

知識庫 (RAG)自訂工具MCP(client/server)
解決查靜態文件接你的活系統/API跨平台互通工具
資料不常變即時、會變看裡面包什麼
方向app 內查app → 你的 APIclient↔server 兩端

Agent vs Chatflow(拿到工具結果後)

Agent(模型自由總結)Chatflow(忠實呈現)
特性彈性、能對話一字不差、流程固定
風險總結那步可能改寫/幻覺零改寫
適合探索式、可容錯錯不得的資料(財務/醫療/法遵)

這版實測踩到的坑 / 限制(Dify 1.14)

怎麼回事 / 怎麼繞
MCP 發佈按鈕找不到不在「發佈」下拉、不在「功能」;只在 chat/agent app 的「概覽」頁。chatflow/workflow 沒有(UI 未接)。
工具 → MCP 分頁那是「接別人的」(client),不是發佈你的。
內部系統被擋 (SSRF)Dify 封鎖私有 IP → 放行信任位址,或走公開網域 443。
token 過期用系統的「不過期整合金鑰」。
工具成功但答案是假的弱模型拿到真資料仍改寫 → 錯不得的資料改用 Chatflow 忠實呈現,別讓 Agent 自由總結。
功能面板「功能」裡是開場白/語音/檔案上傳等體驗開關,沒有 MCP。
Dify 功能面板:沒有 MCP
「功能」面板 = 對話體驗開關,這裡沒有 MCP(別在這找)
PART 四 · 企業落地:授權架構(AAD / SSO)

🔐 企業授權:公司要 AAD / SSO 怎麼接

Dify 的 MCP 端點只有「一個靜態 Bearer token」,做不了 Azure AD(Entra ID)/ OIDC——而且它跑的是舊協定 2024-11-05,連 MCP 原生 OAuth 都沒有。所以企業級授權(AAD / SSO)一定要在 Dify 前面加一層 gateway,Dify 藏在後面、只吃內部 token。

flowchart LR
  C(("🤖 MCP client
呼叫方")):::c AAD[("🔑 Azure AD
Entra ID")]:::aad C -.->|"先拿 token
client credentials"| AAD C ==>|"① 帶 AAD token"| GW["🛡️ Gateway
驗 Entra JWT"]:::gw GW -.->|"驗簽 / issuer / aud"| AAD GW ==>|"② 換內部 token 轉發"| EP["🔌 Dify / n8n MCP 端點
私網 · 強 token"]:::ep EP ==> SYS[("🗄️ 你的系統
RAG · API · DB")]:::sys classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef gw fill:#fef3c7,stroke:#d97706,color:#78350f classDef ep fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef sys fill:#dcfce7,stroke:#16a34a,color:#14532d

呼叫方先跟 Entra 拿 token(服務對服務走 client credentials,不用真人登入)→ 帶進 gateway → gateway 驗簽 / issuer / audience → 驗過才換內部 token 轉給後面。AAD 這關永遠在 gateway,不在 Dify。

Gateway 選項(Microsoft 環境天生合)

做法適合
Azure API Management(validate-jwt 對你們 tenant 驗)企業標準;要 API 治理 / 配額 / log 一起
oauth2-proxy + Entra OIDC輕量、自架、放 nginx 前面
App Service / App Gateway 「Easy Auth」host 在 Azure 時,幾乎零程式
MCP client 端的實務坑:因為 Dify 是舊協定沒 OAuth,client(Claude / Cursor)要能塞自訂 Authorization header 帶 AAD token;token 取得 / 刷新得你或 gateway 處理。各家 client 支援度不同,先驗。
通則:Dify / n8n 都適合當 gateway「後面」的能力提供者,不適合當「對外那道 AAD 門」。門交給 gateway,後面只吃內部 token。

完整版:n8n 當「AAD-aware 中間層」,Dify 在後面

flowchart LR
  AG(("🤖 Agent
呼叫方")):::c ENTRA[("🔑 Entra ID
Azure AD")]:::aad AG ==>|"① AAD token"| GW["🛡️ Gateway
驗 Entra(對外門)"]:::gw GW -.->|"驗 JWT"| ENTRA GW ==>|"② 放行"| N8N["🔗 n8n
AAD-aware 編排層"]:::n8n N8N -.->|"③ 用 Entra 憑證拿 token"| ENTRA N8N ==>|"④ 帶 AAD token 打"| SYS[("🗄️ AAD 保護的內部系統")]:::sys N8N ==>|"⑤ 內部 token · 免 AAD"| DIFY["🖥️ Dify
RAG / 能力提供"]:::dify N8N ==>|"⑥ 匯總 → 回 Agent"| AG classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef gw fill:#fef3c7,stroke:#d97706,color:#78350f classDef n8n fill:#fee2e2,stroke:#dc2626,color:#7f1d1d classDef sys fill:#dcfce7,stroke:#16a34a,color:#14532d classDef dify fill:#e0f2fe,stroke:#0284c7,color:#075985

① Agent 帶 AAD token → ② gateway 驗 Entra 放行 → n8n 進場 → ③ n8n 用 Entra 憑證(client credentials)拿 token → ④ 帶 token 打「AAD 保護的系統」→ ⑤ 另一手用內部 token 打 Dify(Dify 不吃 AAD) → ⑥ n8n 匯總回 Agent。

常見誤解:Dify 不是「AD 驗完的下一站」。n8n 去 AD 拿 token,是為了打受 AAD 保護的系統;打 Dify 走的是內部 token(Dify 自己做不了 AAD)。AD 那關永遠在:對外 = gateway;對受保護下游 = n8n 的 Entra 憑證。都不在 Dify。

授權集中:別在 Dify 養第二套權限

「某信箱能進哪隻 API / 哪個 RAG」這種細粒度授權——關鍵是把政策放一個地方(Entra roles/groups,或中央 PDP),由 PEP 執行;Dify 待在信任邊界後面、不做授權。別在每個 app 重做一套權限(那正是 AD 已經幫你做的事)。

flowchart LR
  AG(("🤖 Agent
帶 AD token
身分 + roles")):::c ENTRA[("🔑 Entra ID
身分 + App Roles/Groups")]:::aad PDP["📜 PDP 中央授權政策
Entra roles / OPA"]:::pdp PEP["🛡️ Gateway / n8n
PEP:驗身分 + 執行授權"]:::pep AG ==>|"AD token"| PEP PEP -.->|"authN 驗簽"| ENTRA PEP -.->|"authZ:能碰 RAG-7 嗎?"| PDP PDP -.-> ENTRA PEP ==>|"✅ 放行 · 內部 token"| DIFY["🖥️ Dify / API
能力提供(信任邊界後)"]:::dify DIFY ==> RES[("🗄️ 100 API · 30 RAG
各自資源")]:::res classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef pdp fill:#fce7f3,stroke:#db2777,color:#831843 classDef pep fill:#fef3c7,stroke:#d97706,color:#78350f classDef dify fill:#e0f2fe,stroke:#0284c7,color:#075985 classDef res fill:#dcfce7,stroke:#16a34a,color:#14532d
角色做什麼
身分 + 政策Entra ID證明你是誰 + App Roles/Groups(誰有哪些資源的權)
PDP(政策決策)Entra roles / OPA回答「user X 能碰 RAG-7 嗎」——只養這一套
PEP(政策執行)Gateway / n8n驗身分 + 問 PDP + 放行 / 擋
能力提供Dify / API信任邊界後,只吃內部 token,不做授權
一句話:身分集中在 Entra、授權決策集中在一個 PEP/PDP、能力分散在 Dify/各系統。權限只養一套,不要每個 app 各養一套。這也是「對外 MCP gateway」授權設計的核心。

最終架構:MCP 在外、API 在內、n8n 依 level 挑 key

把上面全部收斂成「大企業 × 分級」場景的定案骨架:敏感內容分知識庫、每庫一個 Dify app(各自 API key)、n8n 驗身分後依 level 挑 key 打(內部走 REST API,Dify 不用做 MCP);對外要標準化工具才由 n8n 出 MCP。

flowchart LR
  EXT(("🤖 外部
AI client / 系統")):::c ENTRA[("🔑 Entra ID")]:::aad N8N["🛡️🔗 Gateway / n8n
驗身分 + PEP 依 level 路由
對外:MCP 或 API"]:::n8n EXT ==>|"AD token"| N8N N8N -.->|"authN + authZ"| ENTRA N8N ==>|"key① 一般 · REST API"| A1["🖥️ Dify app 一般"]:::d N8N ==>|"key② 經理"| A2["🖥️ Dify app 經理"]:::d N8N ==>|"key③ 處長+"| A3["🖥️ Dify app 機敏"]:::d A1 ==> K1[("📚 KB-一般")]:::k A2 ==> K2[("📚 KB-經理")]:::k A3 ==> K3[("📚 KB-財務/薪資")]:::kr classDef c fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985 classDef k fill:#dcfce7,stroke:#16a34a,color:#14532d classDef kr fill:#fee2e2,stroke:#dc2626,color:#7f1d1d
原則做法
MCP 只在最外層對 AI client 才由 n8n 出 MCP;對一般系統出 API 即可
內部一律 REST APIn8n 用各 app 的 API key 打 Dify /v1/chat-messages,Dify 免裝 mcp-server
per-level = 分庫分 app敏感(財務/薪資)物理分知識庫;n8n 依授權挑對應那把 key
Dify 不做授權信任邊界後,只提供「該庫的 RAG」能力

n8n 實作 sample:節點怎麼設 + 走向

agent 直連 n8n、n8n 自己驗 AAD(不用 gateway)。⬇️ 下載可匯入的 n8n workflow(JSON) → n8n 右上「⋯ → Import from File」。五個節點、一條線:

#節點做什麼
1Webhook(agent 直連入口)agent 帶 Authorization: Bearer 〔AAD token〕 + {query} POST 進來
2取 Entra JWKS抓 Entra 公鑰(驗簽用)
3Code(PEP · 自驗)n8n 自己驗 JWT 簽章 + exp/aud → 讀 roles → 決定 level → 挑對應 Dify key
4HTTP Request用挑到的 key 打 Dify /v1/chat-messages(內部 REST,不走 MCP)
5Respond回傳答案給 agent

走向一條直線:Webhook → 取 JWKS → Code(自驗+路由)→ HTTP(打 Dify)→ Respond唯一不可免的「外部」是 Entra 本身(拿公鑰驗 token),中間那層 gateway 拿掉了。核心在第 3 個 Code 節點(驗簽 → roles → level → key):

const roles = payload.roles || [];            // Entra App Roles
let level='general', keyEnv='DIFY_KEY_GENERAL';
if (roles.includes('RAG.Sensitive'))    { level='sensitive'; keyEnv='DIFY_KEY_SENSITIVE'; }
else if (roles.includes('RAG.Manager')) { level='manager';   keyEnv='DIFY_KEY_MANAGER'; }
// → 用 $env[keyEnv] 那把 key 打對應的 Dify app(分級的知識庫)

n8n 要設 env:NODE_FUNCTION_ALLOW_BUILTIN=crypto(讓 Code 能用 crypto 驗簽)、ENTRA_JWKS_URL(https://login.microsoftonline.com/〔tenant〕/discovery/v2.0/keys)、EXPECTED_AUDDIFY_BASEDIFY_KEY_GENERAL / MANAGER / SENSITIVE。正式環境記得快取 JWKS,別每次都打 Entra。

更正常見誤解(重要):agent 是 M2M,不用 redirect URI。redirect URI 是互動式使用者登入(auth code flow)才要的。你的 agent 是機器對機器 → 走 client credentials flow:在 Entra 註冊一個 App、給它 client ID + secret / 憑證、把「受保護資源 / RAG」做成 App Role(如 RAG.Sensitive)授權給該 agent。agent 拿到的 token 就帶 roles claim,n8n 讀它路由。要你跟 Entra 申請的是:tenant、App 註冊、client secret、App Roles——不是 redirect URI。
這版 n8n 自己驗簽了:Code 節點用 Entra JWKS + 內建 crypto 驗 RS256 + 檢查 exp/aud → 偽造 token 過不了、也不用 gateway。反過來,如果公司已經有 API 閘道(APIM / oauth2-proxy),也可以把驗簽交給它、n8n 只讀已驗的 claims——兩種都行,差別只在你要不要那層 gateway。

身分怎麼進來:人登入 vs 機器 agent

同一個 n8n,兩種入口都能接——不是二選一。分水嶺是人登入(互動)還是機器 / service(M2M)。共通點:身分永遠從 Entra 發(n8n 生不出來);差別在「token 怎麼到手」+「拿到後怎麼授權」:人 → per-person 分級路由;機器 → 取被授權的固定那份(不分級)

flowchart LR
  U(("👤 人 / 員工")):::u
  M(("🤖 機器 / service")):::m
  ENTRA[("🔑 Entra")]:::aad
  N8N["🔗 同一個 n8n
兩種入口都接"]:::n8n U ==>|"/login → auth-code(拿員工身分)"| N8N M ==>|"M2M token(app 身分)"| N8N N8N -.->|"驗身分"| ENTRA N8N ==>|"人:per-person 分級路由"| D1["🖥️ 依 level 的 Dify app"]:::d N8N ==>|"機器:取被授權的固定那份"| D2["🖥️ 該 service 的 Dify app(固定)"]:::d classDef u fill:#dbeafe,stroke:#2563eb,color:#17335e classDef m fill:#e0e7ff,stroke:#4338ca,color:#312e81 classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985

情境一|人登入(1 萬員工多半是這個)—— n8n 當網頁節點,auth-code + redirect URI:員工被導去 Entra 登入,登入完 Entra 把 code callback 回 n8n 的 webhook(= redirect URI),n8n 用 code 換到「這個員工」的 token(帶 roles)→ 依 level 分流。拿到員工身分 → 做得到 per-level;這裡就要 redirect URI。 ⬇️ 這條的 n8n sample(auth-code 人登入版,8 節點:/login → 導去 Entra → /callback 換 token → 取 JWKS → 驗簽(RS256 + exp/nbf/iss/aud)→ 分流) — 這版一樣真驗簽,自己捏的 token 過不了。

flowchart LR
  U(("👤 員工")):::u
  ENTRA[("🔑 Entra 登入")]:::aad
  WH["🔗 n8n webhook
= redirect URI"]:::n8n PEP["🛡️ n8n:驗 token + 依 roles 分流"]:::pep DIFY["🖥️ 對應的 Dify app(分級)"]:::d U ==>|"① 開入口"| WH WH ==>|"② 導去 Entra 登入"| ENTRA ENTRA ==>|"③ callback code 回 n8n"| WH WH ==>|"④ 用 code 換 token(HTTP)"| ENTRA WH ==>|"⑤ 拿到『員工』token → 驗 → roles"| PEP PEP ==> DIFY classDef u fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef n8n fill:#fef3c7,stroke:#d97706,color:#78350f classDef pep fill:#fde68a,stroke:#b45309,color:#78350f classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985

情境二|機器 agent(M2M)—— client credentials / Managed Identity,無 redirect:service / 系統自己跟 Entra 拿 token(或更好:Managed Identity / Entra Agent ID 平台自動發、免管 secret)。關鍵觀念:M2M 沒有「人」的身分,只有「app / service 身分」——它的授權是「這個 service 被授權讀哪一份資料」,通常是一份固定範圍,不做 per-person 分級路由所以流程更單純:驗這個 service 的 token → 確認它有那份資料的權 → 打它被授權的那個 Dify app(固定那份)。不用 redirect URI。 ⬇️ 這條的 n8n sample(機器自驗 AAD;其中 role→key 代表「這個 service 被授權的那份 KB」,對單一 service 是固定的)

flowchart LR
  A(("🤖 機器 / service
app 身分,非人")):::m ENTRA[("🔑 Entra token 端點")]:::aad PEP["🛡️ n8n:驗 service token
+ 確認資料權(非分級)"]:::pep DIFY["🖥️ 它被授權的
那份 Dify app / KB(固定)"]:::d A ==>|"① client credentials /
Managed Identity 拿 token"| ENTRA ENTRA ==>|"token(app roles = 資料範圍)· 無 redirect"| A A ==>|"② 帶 service token 打 n8n"| PEP PEP ==> DIFY classDef m fill:#dbeafe,stroke:#2563eb,color:#17335e classDef aad fill:#ede9fe,stroke:#7c3aed,color:#4c1d95 classDef pep fill:#fde68a,stroke:#b45309,color:#78350f classDef d fill:#e0f2fe,stroke:#0284c7,color:#075985
不是二選一——同一個 n8n 兩種都接:人走 /login(auth-code)→ 員工身分 → per-person 分級路由;機器 / 自動流程走 M2M token → 取它被授權的固定那份(不分級)。同一台 n8n 同時有這兩條入口,依「是人還是 service」走不同授權。只是別把 M2M 當成一個人去分級——M2M 沒有人的身分,只有 app 身分 + 資料範圍。

不是投影片:可 clone 就跑的參考實作

上面的 sample 光貼在頁面上,說服力停在「看起來對」。所以另外做了一個跑得起來的參考實作——docker compose up 之後跑測試,親眼看到它擋掉偽造 token、依職級路由到不同知識庫

✅ 端到端測試 8/8 通過(真的 n8n 1.64 + mock Entra + 分級後端):
⬇️ 下載參考實作(tar.gz,含 mock IdP + n8n + 測試 + 執行紀錄)

解開後:docker compose up -d --builddocker compose run --rm tests。裡面的兩支 workflow 就是上面可下載的同一份(只多了 active 供 CLI 匯入)。為什麼偽造 token 過不了:mock IdP 的私鑰只有它自己有,JWKS 只公布公鑰;n8n 用公鑰 crypto.verify(RS256) 驗簽,沒私鑰就簽不出有效簽章。

📚 回課綱總表 →