实用提示:Spring AI 实战指南——使用 OAuth 保护 MCP 服务器安全
《实用笔记》操作指南:Spring AI 实战方案——利用 OAuth 保护 MCP 服务器:为采用该模式的团队提供契约、校验规则以及可直接插入的代码片段。
以下笔记为“Spring AI Recipe:使用OAuth保护MCP服务器”提供了一条实用的实施路径。重点在于契约、校验以及可直接插入的代码占位符,而非激励性描述。 在完成概览阶段时,首先明确契约内容:所需输入、成功信号以及部分失败时的处理方式。这样的清单能确保后续的代码修改保持一致性。 将配置信息置于应用程序代码之外。环境文件、密钥存储以及功能开关应集中存放于一个位置,以便操作人员无需查看整个系统结构即可进行审计。
创建授权服务器
将“创建授权服务器”这一阶段视为可度量的对象来处理,效果最佳。在扩大范围之前,需记录一份理想的操作流程、一个故障案例以及回滚说明。同时文档化正常处理路径和恢复路径。重试机制、人工审核环节以及死信处理都属于产品功能的一部分,而非后续需要补充的内容。应提供具有明确结构规范和清晰副作用标识的工具,这样主机才能在自动批准之前知道哪些调用会改变系统状态。
implementation 'org.springaicommunity:mcp-authorization-server:0.1.14'
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth ->
auth.anyRequest().authenticated())
.formLogin(withDefaults())
.with(
McpAuthorizationServerConfigurer.mcpAuthorizationServer(),
withDefaults())
.build();
}
}
spring:
application:
name: recipes-authorization-server
security:
oauth2:
authorizationserver:
client:
default-client:
token:
access-token-time-to-live: 1h
registration:
client-id: "myclient"
client-secret: "{noop}mysecret"
client-authentication-methods:
- "client_secret_basic"
authorization-grant-types:
- "authorization_code"
redirect-uris:
- "http://localhost:6274/oauth/callback"
- "http://127.0.0.1:6274/oauth/callback"
scopes:
- general-access
- meteorology
- administration
user:
name: craig
password: letmein
server:
port: 9999
保护 MCP 服务器安全
将“保护 MCP 服务器安全”这一环节视为可度量的工作对象来处理效果最佳。在扩大范围之前,先记录一份理想的操作流程、一个故障案例以及回滚说明。 优先使用小型且可测试的单元,而非庞大的脚本。当某个步骤出现故障时,故障应指向单一的责任模块,而非复杂的流程链。 提供具有明确结构规范和清晰副作用标注的工具。主机需要在自动批准之前知道哪些调用会修改状态。
implementation 'org.springaicommunity:mcp-server-security:0.1.14'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server'
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:9999
@Configuration
@EnableWebSecurity
class McpSecurityConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth ->
auth.anyRequest().authenticated())
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
mcpAuthorization.authorizationServer(issuerUrl);
}
)
.build();
}
}
测试 MCP 服务器
将“测试 MCP 服务器”这一阶段视为可度量的工作面,效果最佳。在扩大范围之前,需记录一份理想运行示例、一个失败案例以及回滚说明。 应将此阶段视为输入与已验证输出之间的契约。为相关成果命名,明确成功标准,杜绝默许的半完成状态。 需提供具有严格结构定义和明确副作用标识的工具。主机在自动批准之前必须清楚哪些调用会修改状态。 将“测试 MCP 服务器”这一阶段视为可度量的工作面,效果最佳。在扩大范围之前,需记录一份理想运行示例、一个失败案例以及回滚说明。 应将配置置于应用程序代码之外。环境文件、密钥存储及功能开关应集中存放,以便操作人员无需查看整个系统结构即可进行审计。
保护单个工具的安全
在“保障单个工具安全”阶段,应在修改代码之前明确输入参数、该步骤的负责人以及结束标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。 需同时记录正常流程与故障恢复流程。重试机制、人工审核环节以及错误处理都是产品本身的组成部分,而非后续需要补充的功能。 在网关处进行身份验证,在数据层进行重新授权。仅凭承载令牌并不能作为租户边界。
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
class SecurityConfig {
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuerUrl;
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/mcp").permitAll();
auth.anyRequest().authenticated();
})
.with(
McpServerOAuth2Configurer.mcpServerOAuth2(),
(mcpAuthorization) -> {
mcpAuthorization.authorizationServer(issuerUrl);
}
)
.build();
}
}
@PreAuthorize("hasAuthority('SCOPE_meteorology')")
@McpTool(
name = "get-weather-for-zipcode",
description = "Gets the weather for a given zipcode",
annotations = @McpTool.McpAnnotations(
openWorldHint = false,
destructiveHint = false,
idempotentHint = true))
Weather getWeatherForZipcode(
@McpToolParam(description = "The zipcode to get weather for")
String zipcode) {
var context = SecurityContextHolder.getContext();
var username = context.getAuthentication().getName();
return new Weather(
zipcode,
"Raining cats and dogs",
78.0f,
username);
}
操作检查清单
在制定操作检查清单时,需明确代码修改前的输入参数、各步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新执行相应步骤,而无需猜测隐藏状态。
在功能结果旁记录处理时间以及令牌或查询成本。提前了解成本情况,可避免在系统从演示环境切换到共享环境时出现意外费用。
在网关处进行身份验证,在数据层再次授权。仅凭承载令牌并不能作为租户边界。
编写简短的操作手册:说明如何轮换密钥、如何清空队列、以及如何回滚上一次的数据导入操作。
将配置信息与应用程序代码分开存放。环境文件、密钥存储和功能标志应集中管理,以便操作人员无需查看整个系统结构即可进行审计。
在网关处进行身份验证,在数据平面处重新授权。仅凭承载令牌并不能构成租户边界。
在升级技术栈之前,先冻结各版本,为关键流程记录完整日志,并明确回滚步骤。共享环境需要设置速率限制、进行租户检查,同时指定专人负责密钥轮换。与其追求花哨的一次性演示,不如注重扎实的可靠性。
关于01496ca9e17e的批量说明:请将提供商密钥存放在仓库之外,设定单会话令牌上限,并将日志与评估用配置文件放在一起,以便后续模型更换时保持对比一致性。
在强化措施的第0阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新执行该步骤,而无需猜测隐藏状态。应将此阶段视为输入与验证后输出之间的契约:为相关成果命名,定义成功判定标准,并拒绝默许部分完成的情况。
强化措施细节0/819:需记录该措施的耗时、错误类型以及令牌消耗情况,然后依据固定的评估标准而非主观判断来决定是否保留该变更。
在处理强化措施的第一阶段时,首先写下相关约定:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合规范。 应将配置信息与应用程序代码分开。环境文件、密钥存储以及功能开关应集中存放于一个位置,这样操作人员无需查看整个系统结构即可进行审计。
强化措施细节 1/819:针对该措施记录墙钟时间、错误类型以及令牌消耗情况,然后依据固定的评估标准而非个人经验来决定是否保留该变更。
将强化措施的第二阶段视为可度量的对象来处理效果最佳。在扩大范围之前,先记录一份理想的运行日志、一个失败案例以及回滚说明。 相比庞大的脚本,更应采用小型且可测试的单元。当某个步骤出现故障时,故障原因应能明确指向某个具体的责任模块,而非整个复杂的流程。
强化措施细节2/819:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。
在强化措施的第3阶段,应在修改代码之前明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。应在功能结果旁记录运行时间以及代币或查询成本。提前了解成本情况,可避免在从演示环境过渡到共享环境时出现意外费用。
强化措施细节3/819:为该记录测量运行时间、错误类型以及代币消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。
在处理强化措施的第4阶段时,首先写下相关契约:所需的输入参数、成功信号以及部分失败时的处理方式。这样的检查清单能确保后续的代码修改始终符合要求。
同时记录正常流程和故障恢复流程。重试机制、人工审核环节以及错误处理方式都是产品本身的组成部分,而非后续才添加的完善措施。
强化措施的第4/819条细节要求:先测量该环节的耗时、错误类型以及令牌消耗情况,再依据固定的评估标准而非个人经验来决定是否保留该变更。
将强化措施的第5阶段视为可量化的目标面来处理效果最佳。在扩大范围之前,先记录一份理想的操作示例、一个故障案例以及回滚说明。
要把这一阶段视为输入参数与验证后输出结果之间的契约。为相关文档命名,明确成功判定标准,杜绝默许部分完成的情况。
强化措施细节5/819:记录该条说明的运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。
对于强化措施的第6阶段,在修改代码之前需明确输入参数、该步骤的负责人以及完成标准。操作人员应能够从已知的检查点重新运行该步骤,而无需猜测隐藏状态。配置应置于应用程序代码之外,环境文件、密钥存储以及功能标志应集中存放于一个操作人员可以审核的位置,无需查看整个系统结构。
强化措施细节6/819:记录该条说明的运行时间、错误类型以及令牌消耗情况,然后依据固定的问题集而非个人经验来判断是否保留该变更。