← 返回文章列表

Dify MCP 集成实验(05):认证体系——MCP Server 如何做企业级认证?

1. 业务场景

先讲一个我们实际遇到的场景。

一家做客服工单 SaaS 的公司,要接客户现场的真实数据源(mock ERP)——但客户的安全评审第一关就是认证:没有认证的 MCP 接入根本过不了。企业数据源的鉴权有三种典型场景,由简到繁:内部服务用预共享密钥(header 静态 token)、系统级服务账号(client_credentials)、用户级授权(OAuth 2.1 授权码 + PKCE——管理员授权后,普通用户查询走已授权 token)。

我们第一次接这类需求时,第一反应是「认证不就是加个 header 嘛」。真正动手才发现——认证是三层模式、失败形态、token 生命周期的组合题:OAuth 授权码流程涉及 Console 配置入口、回调地址、授权页面、刷新触发,每一步没实测过,客户问细节就露怯。我们把三种模式全部跑通,把失败形态整理成表,才敢写进服务描述。

这不是个例。任何「企业客户有真实数据源要接」的集成都是这个模式:源码确认了认证层实现(PKCE S256 / 三种 grant type / MCPClientWithAuthRetry),但实操流程(Console 配置入口、回调地址、授权页面、刷新触发)全部未实测——不实测,交付时就是两眼一抹黑。

2. 场景痛点

这个流程的痛点,在认证落地时体现得最直接:

本质上,认证不是「加个头」——三种模式、失败形态、token 生命周期都要实测,才能写进服务描述

3. 方案:为什么是 SDK 2.0 AuthSettings

实测 Dify 连接 MCP server 的三种鉴权方式全流程:自定义 headers → client_credentials → OAuth 2.1 授权码 + PKCE。

选它的理由:

这篇文章我们就用它给受保护的 ERP 数据源(get_erp_order)配齐三种鉴权方式,走通全流程并沉淀失败形态表。

4. 整体架构

graph TD server["本地开发机:dify107_05_auth_server(:8905/mcp,受保护)"] erp["get_erp_order(需 token)"] oauth["mock OAuth 服务器(uvicorn :8906)"] md["/.well-known/oauth-authorization-server(metadata)"] authorize["/authorize(PKCE 授权页,auto-approve)"] token["/token(authorization_code / client_credentials / refresh_token)"] dify["Dify 服务器"] api["api(含 /console/api/mcp/oauth/callback 回调端点)"] web["web(工具页 MCP tab:鉴权配置)"] cb["Console 授权回调 → token 存库"] server --> erp server --> oauth oauth --> md oauth --> authorize oauth --> token server -- "MCP 调用" --> dify dify --> api dify --> web dify --> cb

链路很清晰:受保护 server(get_erp_order)↔︎ mock OAuth 服务器(metadata/authorize/token)↔︎ Dify(api 回调端点 + web 鉴权配置)。关键设计是三种鉴权模式在同一个 server 上递进实测,失败形态逐类记录——每种模式都验证到「授权后调用成功」为止。

5. 模块设计

5.1 Server 侧:AuthSettings + token_verifier(SDK 2.0 自动托管)

from mcp.server.mcpserver import MCPServer, AuthSettings

from mcp.server.auth.middleware import token_verifier

server = MCPServer(

    name="dify107_05_auth_server", version="1.0.0",

    auth=AuthSettings(

        issuer_url="http://host.docker.internal:8906",

        resource_server_url="http://localhost:8905",

        required_scopes=["erp:read"],

    ),

    token_verifier=token_verifier,   # 未授权 401 + WWW-Authenticate

)

SDK 自动托管 /.well-known/oauth-protected-resource/mcp(RFC 9728 格式,authorization_servers 指向 issuer)。

5.2 三模式实测链路

模式 流程
一:自定义 headers provider 配置 headers {Authorization: Bearer ***} → 试连带 header 通过 → 工具拉取成功
二:client_credentials mock metadata grant_types=[client_credentials] → provider 配 client_id/secret → POST /token(Basic Auth)→ authed
三:OAuth 授权码 + PKCE DCR 注册 → authorization_url(PKCE S256,state 存 Redis)→ auto-approve 授权 → Dify 回调端点 → token 交换 → authed

5.3 失败形态表(验收/排查用)

场景 报错形态 排查指引
无 token 调工具 server 401 {"error":"invalid_token"} → Dify tool failed 检查 provider authed/headers
client_credentials 凭据错 mock 401 invalid_client → Dify「Client credentials flow failed...blocked by SSRF protection」(误判 先本地 curl 复现区分 mock 401 vs squid 403
metadata 缺字段 Dify「Failed to discover OAuth metadata from server」 metadata 必须含 authorization_endpoint/token_endpoint/response_types_supported
authorize 缺参数 400 invalid_request code_challenge/redirect_uri/state 必填
PKCE 校验失败 400 invalid_grant PKCE 校验失败 code_verifier 与 code_challenge 不匹配
token 过期/无效 server 401 → MCPClientWithAuthRetry 刷新(refresh_token) 刷新失败则清凭据重新授权
授权后工具空 tools=[],「Tool with name xxx not found」 1.16.1 限制:工具拉取只在 create,授权后手动补/重导

6. 运行验证

验证项 预期 结果
header 鉴权 配置后调用成功;缺 header 报错 通过
client_credentials 配置后自动鉴权调用成功 通过(auth 200 + 调用 succeeded)
OAuth 授权码 + PKCE 授权流程走通、token 存库、授权后调用成功 通过(全自动化,无真实浏览器)
token 自动刷新 401 触发 MCPClientWithAuthRetry 用 refresh_token 刷新 机制源码确认(机制确认,端到端过期刷新未实测)
失败形态表 5 类以上失败形态 + 排查指引 通过(见 4.3)
认证能力清单 OAuth 2.1 + PKCE / client_credentials / refresh 自动刷新 / DCR / 资源发现 通过

7. 实战坑

现象 修复
AccessToken 必填 client_id SDK 2.0 token_verifier 返回 AccessToken 缺 client_id 报 ValidationError token 校验/构造时带上 client_id(实测)
metadata 必填字段 client_credentials 模式 metadata 缺 authorization_endpoint/response_types 也报错 Dify OAuthMetadata 模型强制,grant_types 决定模式(实测)
client_credentials 用 Basic Auth mock /token 只读 form → invalid_client 401 → 被 ssrf_proxy 误判「SSRF blocked」 解析 Authorization: Basic ***;先本地 curl 复现区分(实测)
回调端点完成交换 POST auth 传 code 报「State parameter is required」 code 必须走回调端点 GET /console/api/mcp/oauth/callback?code&state(state 从 Redis 取 code_verifier)(实测)
授权后 tools 不刷新 1.16.1 工具拉取只在 create,授权后 tools 空 手动补 DB tools 字段(含 outputSchema 才字段展开)或删了重导(实测)
token 校验与 store mock 重启 TOKEN_STORE(内存)清空 → Dify DB token server 不认 → 401 演示环境放宽前缀校验;生产用共享存储/introspection(实测)
sync/async 坑 sync def 端点里 request.json()/form() 是 async 必须 async def + await(实测)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过