Spring AI 接 MCP,最危险的不是连不上,而是上线后越权:6 个权限、安全、隔离坑
本地能跑通,不等于你敢上线
如果你最近在做 Spring AI + MCP,大概率已经经历过这个阶段:
tools/list通了- 模型能选中工具了
- 本地 demo 也确实跑起来了
- 于是团队里开始有人说,可以准备往测试环境推了
这时候最容易出现一种错觉:
既然 MCP 已经接通了,剩下的无非就是把功能再补一补。
但真正危险的地方,往往不是“接不通”,而是“接通以后你把什么东西暴露出去了”。
我见过不少项目,卡在的都不是协议层,而是上线前才开始冒头的这些问题:
- 一个本来只想查数据的 tool,最后被做成了半个管理后台入口
- 用户态和服务态授权没拆开,结果谁来调用都拿同一套权限
- Prompt 里写了“只允许当前租户”,服务端却没有真正做强校验
- 排障时把日志全开,最后先把 prompt、参数、返回内容一起送进了日志系统
- 自动发现和自动注册一开,团队自己都说不清当前环境到底暴露了哪些工具
说白了,MCP 连通只解决了“能不能跑”,没有解决“能不能安全地跑到线上”。
真正把系统推到测试环境、灰度环境,甚至生产环境后,问题往往不是出在 MCP 连不上,而是出在下面这些地方:
- 认证到底是用户态,还是服务态
- 工具到底暴露了多少能力
- 多租户数据边界是不是靠模型“自觉遵守”
- 日志和追踪到底该记到什么程度
- 自动发现、自动注册、自动初始化是不是把风险一起自动放大了
如果你最近正在用 Spring Boot + Spring AI + MCP 做 Agent、助手、Copilot 或企业内部智能平台,这篇建议你直接留下。
因为下面这 6 个坑,几乎每一个都不是“写得优不优雅”的问题,而是“上线后会不会翻车”的问题。
这篇不讲怎么接通,只讲怎么避免上线后出事
Spring 官方这几个月在持续推进 AI 和 MCP 相关能力,但官方资料也已经把一个信号讲得很清楚:
- MCP 不只是一个“工具调用协议”,它还涉及认证和授权边界[^1]
- Spring AI 在 MCP 场景下已经给出了专门的安全支持方案和自动配置能力[^2]
- Spring AI 的工具调用、可观测性、日志内容导出,本身就带着明显的安全取舍[3][4]
也就是说,今天的 Spring AI 接 MCP,已经不是“能连通就算做完”的阶段了。
真正的门槛,是你有没有把权限、安全、隔离和审计这些后面的事一起设计进去。
先看一张总览图。
第一个坑:把“连通成功”当成“安全验收通过”
很多人做第一轮联调时,验收标准只有三个:
tools/list能返回- 模型能选中工具
- 工具能成功执行一次
但这顶多说明链路通了,不说明边界对了。
MCP 在官方授权文档里已经明确把认证、授权放到了协议层面考虑,尤其是基于 HTTP 的远程 MCP 场景,客户端和服务器需要通过标准授权机制建立可信访问关系。[^1]
Spring 团队在讲 MCP 安全接入时,也明确强调了令牌传播、客户端认证、资源服务器保护这些问题。[2][5]
换句话说,真正要验收的不是“能不能调”,而是:
| 你该验什么 | 只看连通时经常漏掉什么 |
|---|---|
| 谁能访问 MCP Server | 把内网能访问的人都默认当成可信调用方 |
| 调用拿的是什么令牌 | 用户令牌和服务令牌混在一起 |
| 某个工具是否该被当前会话调用 | 工具一旦注册成功,所有请求都能碰到它 |
| 拒绝访问时会发生什么 | 没有明确的 401、403 处理和回退逻辑 |
更现实一点地说,本地演示环境里“连通成功”经常只是说明:
你在一个低风险、低并发、单用户、单租户场景下,看到了 happy path。
这和上线是两回事。
怎么改
- 把“调用成功率”从验收表里降级成基础项
- 把“未授权时是否拒绝”“越权时是否拦截”“错误时是否可审计”加进主验收项
- 对每个 MCP Server 分开定义信任边界,不要因为都是内部系统就默认互信
第二个坑:没有分清用户态授权和服务态授权
这是接 MCP 时最容易在架构上埋雷的点。
很多团队一开始默认只有一种身份:
- 要么把所有请求都当成“当前用户在发起”
- 要么把所有请求都当成“系统服务自己在发起”
实际上一到线上,这两类流量往往必须拆开。
Spring AI 的 MCP 安全文档专门提到了一类很典型的问题:
应用启动时就可能要做 MCP 初始化,比如工具发现;但这时候压根还没有用户会话,也就不可能天然带上用户令牌。文档把这类问题称为 “hybrid flow” 场景,并建议显式区分启动阶段和用户请求阶段的认证策略。[^2]
这个问题一旦没拆清楚,后面通常会出现两种坏结果:
- 系统启动依赖用户态令牌,结果一启动就卡死
- 所有工具都走服务账号,结果上线后每个用户都拿着同一把“大钥匙”
你应该怎么理解这件事
可以把它想成两条完全不同的链路:
-
系统启动链路
这条链路关心的是:应用能否完成初始化、能否发现工具、能否建立基础连接。 -
用户请求链路
这条链路关心的是:当前用户有没有资格调用某个工具,调用后能看到哪些数据。
这两条链路如果共用一套令牌策略,十有八九后面会出事。
一个很值得你留意的官方细节
Spring AI 的 MCP 安全文档目前明确写了:
- 支持基于 OAuth2 的安全客户端
- 提供启动期认证和请求期认证的区分方案
- 当前说明中还特别提到这套支持主要面向
McpSyncClient场景[^2]
这意味着如果你现在的系统本身就是异步、反应式或多阶段代理编排架构,别看到 demo 通了就默认“这套认证流在线上一定自然成立”。
怎么改
- 启动阶段和用户请求阶段分开设计认证策略
- 明确哪些调用可以用服务账号,哪些调用必须带当前用户上下文
- 把“用户身份传播失败”当成需要专门压测和回归测试的场景
第三个坑:把内部能力原封不动暴露成 MCP Tool
这个坑在本地特别不容易被意识到,因为一开始大家通常只想快点把功能跑出来。
Spring AI 的工具调用机制本质上是这样的:
- 你把工具定义暴露给模型
- 模型根据工具名称、描述和输入 schema 判断要不要调
- 真正执行工具的是你的应用,不是模型[^3]
这句话听起来很普通,但里面有一个非常关键的结论:
工具定义本身,就是你的“能力暴露面”。
从机制上看,只要某个工具进入了模型可见范围,它的名字、描述、参数结构就已经开始影响模型决策。
所以如果你把内部服务接口原封不动包装成 MCP Tool,常见问题会马上跟上来:
- 工具粒度过粗,一个 tool 就能做太多事
- 参数过宽,用户可以间接触发高风险操作
- 描述写得太“智能”,模型更容易在边界不清时误选工具
- 把调试接口、管理接口、批量接口和普通查询接口混在一起注册
一个很实用的判断标准
不要问“这个接口能不能封成 tool”。
要问“如果模型把这个 tool 用错一次,代价能不能接受”。
如果答案是否定的,那这个工具就不该以当前形态暴露出去。
怎么改
- 把高风险写操作和低风险读操作拆成不同工具集
- 给工具做最小能力封装,不要把内部管理 API 直接抬到 MCP 层
- 严格收窄 schema,只暴露当前场景真的需要的参数
- 工具描述写清边界,不要为了“提高命中率”把描述写成万能入口
一个很常见但很危险的反例是这样:
@Tool(description = "Manage all user operations")
public String manageUser(String action, String userId, String tenantId, String payload) {
// create / update / delete / reset / grant role ...
}
这种工具本地演示也许很方便,但一上线就几乎等于把一堆管理能力打包交给了模型路由层。
更稳妥的思路通常是:
@Tool(description = "Get current user's order summary")
public OrderSummary getCurrentUserOrderSummary() {
// derive user and tenant from server-side context
}
这里最重要的不是代码长短,而是边界是不是被收紧了。
第四个坑:把多租户隔离写进 Prompt,却没写进服务端
这是很多 AI 项目最典型的“看起来有约束,实际上没边界”问题。
你当然可以在 system prompt 里写:
- 只能访问当前租户数据
- 不要查询其他部门信息
- 只允许读取当前用户有权限的订单
但别忘了,Prompt 是行为引导,不是安全边界。
Spring AI 的工具调用文档里强调得很清楚:
模型不会直接访问你的 API,真正的工具执行发生在应用侧。[^3]
这句话反过来读,就是:
真正的权限控制,也必须发生在应用侧。
所以在多租户场景里,真正靠谱的做法从来不是让模型“理解租户隔离”,而是让服务端“强制执行租户隔离”。
线上最常见的错误写法
- 让模型把
tenantId当成普通参数传进来 - 工具实现直接信任这个
tenantId - 最后依赖 Prompt 约束模型“不要传错”
这在单用户测试时往往没感觉,但到了线上,多轮对话、工具重试、上下文拼接、提示词污染一起叠加后,风险会变得非常实际。
更稳的做法
| 场景 | 不推荐 | 推荐 |
|---|---|---|
| 租户识别 | 模型传 tenantId |
服务端从认证上下文解析 |
| 用户身份 | 工具参数里显式传 userId |
服务端从安全上下文提取 |
| 数据过滤 | 先查全量再让模型挑 | 查询阶段就带租户和权限条件 |
| 越权兜底 | 靠 Prompt 提醒 | 靠服务端策略拒绝 |
一句话总结就是:
能从服务端上下文拿到的信息,就不要交给模型自由填写。
第五个坑:日志和追踪不是开得越多越安全
很多后端团队在出问题时第一反应是:
那就把日志全开。
但 AI 场景里,日志开得太猛,往往会先把敏感信息送进日志系统。
Spring AI 的可观测性文档对这一点其实已经很谨慎了。官方默认是:
- 不导出 prompt 内容
- 不导出 completion 内容
- 不记录 tool 的输入参数
- 不记录 tool 的执行结果[^4]
这些默认值之所以保守,不是因为官方不重视排障,而是因为这些内容极有可能包含敏感数据。
看一下官方给出的相关配置项,你就能明白这个取舍非常明确:
spring.ai.chat.observations.log-prompt=false
spring.ai.chat.observations.log-completion=false
spring.ai.chat.client.observations.include-input=false
spring.ai.tools.observations.include-content=false
这就意味着,MCP 上线前你真正要做的不是“全关”或者“全开”,而是分层设计:
| 层级 | 应该记录什么 |
|---|---|
| 业务审计 | 谁在什么时间调用了哪个 tool,是否成功 |
| 运维排障 | 调用耗时、异常类型、重试次数、下游状态码 |
| 安全审计 | 是否越权、是否命中敏感工具、是否跨租户失败 |
| 内容日志 | 仅在极少数受控场景按需采集,默认不放开 |
怎么改
- 默认延续 Spring AI 的保守设置,不要一上来就导出全部内容
- 审计和排障拆开设计,不要让“看得见内容”成为唯一排障手段
- 对高风险 tool 额外记录业务主键、租户信息、调用人,而不是全量 prompt
这件事做对了,你的系统才会既能排障,也不至于先把自己日志系统变成敏感数据池。
第六个坑:自动发现、自动注册、自动初始化一开,能力边界也跟着失控
自动化配置是 Spring 生态的优势,但在 MCP 场景里,它也会放大一个老问题:
你以为自己只是“省步骤”,实际上你可能把暴露面也一起放大了。
Spring AI 官方文档提到,MCP 客户端在启动阶段可能需要做工具发现;安全文档还专门给了用空的 ToolCallbackProvider 来阻止自动工具注册的做法。[^2]
这已经说明一个很现实的问题:
- 自动发现能提高开发效率
- 但如果环境、认证、工具清单没有被严控,自动发现也会把错误能力带进来
另一个必须注意的点是版本和模块成熟度。
Spring AI 当前 MCP 安全支持文档里明确写了,这部分能力带有明显的演进属性,并强调了其面向 MCP Java SDK 社区实现的背景。[^2]
这不代表不能用,而是代表你上线前别做这两件事:
- 看到官方有 starter 就默认所有安全细节都已经替你兜底
- 看到本地 autoconfigure 生效就默认线上环境的认证、初始化、工具集合也完全正确
一个很有代表性的配置片段
官方给出的一个思路,是在需要时主动阻止默认自动注册:
@Bean
ToolCallbackProvider toolCallbackProvider() {
return ToolCallbackProvider.from(List.of());
}
这段代码的价值不在于“把功能关掉”,而在于提醒你:
工具是否进入模型可见范围,应该是显式决策,而不是顺手发生。
怎么改
- 不要让所有环境都共享同一套自动发现策略
- 工具清单做环境级控制,开发、测试、生产分开维护
- 把启动初始化日志纳入验收,确认到底注册了哪些 tool
- 每次发版都核对“本次到底新增暴露了哪些工具能力”
如果你真的准备上线,至少把这张检查单过一遍
Spring AI 接 MCP 上线前检查单| 检查项 | 你要确认什么 |
|---|---|
| 认证链路 | 启动期和请求期是否拆开设计 |
| 令牌策略 | 用户令牌和服务令牌是否分开使用 |
| 工具边界 | 是否存在一个 tool 打包多个高风险能力 |
| 参数设计 | 是否仍在信任模型传入的租户或用户标识 |
| 服务端隔离 | 是否在服务端强制执行租户和权限条件 |
| 审计日志 | 是否能查到谁调用了什么、何时调用、结果如何 |
| 内容日志 | 是否避免把 prompt、tool 参数、结果默认全量落盘 |
| 自动注册 | 是否清楚当前环境到底暴露了哪些 tools |
| 初始化流程 | 启动阶段是否依赖了不该依赖的用户态认证 |
| 回退机制 | 401、403、下游失败时是否有明确处理路径 |
这篇文章最想让你带走的一个结论
Spring AI 接 MCP,真正难的从来都不是“把协议接上”。
真正难的是你要在一个会自动路由工具、会跨上下文、会进入多租户业务的系统里,把边界重新定义一遍。
所以如果你问我,Spring AI 接 MCP 上线前最该先做什么,我的答案不是:
- 先调通更多工具
- 先做更复杂的 Prompt
- 先接更多模型
而是:
先把认证边界、工具边界、租户边界和审计边界定清楚。
因为系统能跑,只说明它开始工作了。
系统敢上线,才说明它开始成熟了。
更多推荐




所有评论(0)