云计算百科
云计算领域专业知识百科平台

为 MCP 服务器接入 OAuth2:基于 Spring Authorization Server 与 Azure API Management 的完整实战(mcp-for-beginners)

  • 教程
  • 文档
  • 人工智能

【免费下载链接】mcp-for-beginners

This open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.

项目地址:
https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners

点击查看 免费下载

本文围绕 mcp-for-beginners 仓库中 05-AdvancedTopics/mcp-oauth2-demo 的实战样例展开,讲解如何用 Spring Boot 把 MCP 服务器同时改造成 OAuth2 授权服务器(Authorization Server)与资源服务器(Resource Server),以 client_credentials 流程为 AI Agent 等机器客户端签发 JWT 访问令牌,并通过 Azure Container Apps 部署、Azure API Management 网关统一验签。读完本文你将掌握从本地 curl 验证到云端容器化部署的完整链路,并理解源码级的安全配置要点。

一、这个 Demo 解决什么问题

在 MCP(Model Context Protocol,模型上下文协议)的实现中,客户端(例如 AI Agent)需要以安全、标准化的方式访问 MCP 服务器及其工具。OAuth2 正是行业标准的授权协议:它允许在不共享用户凭据的前提下,向已认证的客户端签发受限范围的访问令牌。

mcp-oauth2-demo 是一个最小化的 Spring Boot 应用,同时扮演两个角色:

  • Spring 授权服务器(Spring Authorization Server):通过 client_credentials(客户端凭据)流程签发 JWT 访问令牌;
  • 资源服务器(Resource Server):保护自己的 /hello 端点,任何未携带有效令牌的请求都会被拒绝。

它复刻了 Spring 官方博客「Securing Spring AI MCP servers with OAuth2」所展示的配置思路,适合企业级与生产化部署场景。在学习之前,请先确认:

  • 具备 Java 与 Spring Boot 的基础知识;
  • 已熟悉前面章节的 MCP 核心概念;
  • 本地已安装 Maven 或 Gradle(本样例使用 Maven)。

[!WARNING] 这是一个本地学习样例,不是生产授权服务。它使用内存客户端,并在每次启动时重新生成签名密钥。绝不要将共享的、默认的或纳入版本控制的客户端密钥部署到生产环境。详见下文「生产安全」一节。

二、项目结构与源码组成

样例位于仓库的 05-AdvancedTopics/mcp-oauth2-demo 目录,包含四个核心源码文件:

文件作用
Application.java Spring Boot 启动入口
SecurityConfiguration.java 授权服务器 + 资源服务器的全部安全配置
HelloController.java 受保护端点 /hello
SecurityConfigurationTest.java 安全配置的单元测试

构建配置方面,pom.xml 采用 Spring Boot 3.2.5 作为父工程、Java 17,并声明了三个关键依赖:

  • spring-boot-starter-web:提供 REST 端点;
  • spring-boot-starter-oauth2-resource-server:启用 JWT 令牌校验;
  • spring-boot-starter-oauth2-authorization-server:启用 OAuth2 授权服务器能力。

三、快速开始(本地运行)

在项目根目录下,先设置一个唯一的本地密钥并尽可能避免进入 shell 历史记录:

# 使用一个唯一的本地值,并尽可能让它不进入 shell 历史
export OAUTH_CLIENT_SECRET="replace-with-a-random-local-secret"
mvn spring-boot:run

应用启动后(默认端口 8081),依次执行三条命令完成「取令牌 → 调接口」的闭环:

# 获取令牌
curl -u "mcp-client:${OAUTH_CLIENT_SECRET}" -d grant_type=client_credentials \\
http://localhost:8081/oauth2/token | jq -r .access_token > token.txt

# 调用受保护端点
curl -H "Authorization: Bearer $(cat token.txt)" http://localhost:8081/hello

在 PowerShell 环境下,先设置本地密钥再运行 Maven:

$env:OAUTH_CLIENT_SECRET = "replace-with-a-random-local-secret"
mvn spring-boot:run

四、分步测试 OAuth2 安全配置

步骤 1:确认服务器已启动且被安全保护

直接访问根路径,应返回 401 Unauthorized,这证明 OAuth2 安全机制已经生效:

curl -v http://localhost:8081/

步骤 2:用客户端凭据获取访问令牌

以 mcp-client 作为客户端 ID、${OAUTH_CLIENT_SECRET} 作为客户端密钥,向令牌端点发起表单请求,并声明 mcp.access 作用域:

# 获取并打印完整令牌响应
curl -v -X POST http://localhost:8081/oauth2/token \\
-H "Content-Type: application/x-www-form-urlencoded" \\
-u "mcp-client:${OAUTH_CLIENT_SECRET}" \\
-d "grant_type=client_credentials&scope=mcp.access"

# 或者只提取令牌本身(需要 jq)
curl -s -X POST http://localhost:8081/oauth2/token \\
-H "Content-Type: application/x-www-form-urlencoded" \\
-u "mcp-client:${OAUTH_CLIENT_SECRET}" \\
-d "grant_type=client_credentials&scope=mcp.access" | jq -r .access_token > token.txt

步骤 3:用令牌访问受保护端点

# 使用已保存的令牌
curl -H "Authorization: Bearer $(cat token.txt)" http://localhost:8081/hello

# 或直接使用令牌值
curl -H "Authorization: Bearer eyJra…token_value…xyz" http://localhost:8081/hello

返回 Hello from MCP OAuth2 Demo! 即表示 OAuth2 配置工作正常——该响应文本正是 HelloController.java 中 /hello 映射方法的返回值。

五、源码级解析:安全配置如何工作

理解了外部行为后,深入 SecurityConfiguration.java 可以看清三个核心 Bean 的实现原理。

5.1 安全过滤器链:授权服务器 + 资源服务器合体

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();

http.apply(authorizationServerConfigurer);

http.authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.csrf(Customizer.withDefaults());

return http.build();
}

  • apply(authorizationServerConfigurer) 挂载授权服务器默认端点,例如 /oauth2/token 与 /oauth2/jwks;
  • anyRequest().authenticated() 要求所有请求都必须通过认证;
  • oauth2ResourceServer(…).jwt(…) 开启 JWT 资源服务器模式,收到请求时校验 Authorization: Bearer 头中的令牌签名、有效期等属性。

5.2 JWK 源:启动时生成 RSA 签名密钥

@Bean
public JWKSource<SecurityContext> jwkSource() throws Exception {
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair kp = kpg.generateKeyPair();
RSAKey rsaKey = new RSAKey.Builder((RSAPublicKey) kp.getPublic())
.privateKey((RSAPrivateKey) kp.getPrivate())
.keyID("demo-key")
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return new ImmutableJWKSet<>(jwkSet);
}

从源码可以清晰看到:密钥对在每次应用启动时重新生成(RSA-2048,keyID 为 demo-key),并通过 /oauth2/jwks 发布公钥。这正是 README 开头警告「每次启动生成新签名密钥」的直接原因——生产环境必须替换为持久化密钥。

5.3 内存注册客户端:client_credentials + mcp.access

@Bean
public RegisteredClientRepository registeredClientRepository(
@Value("${demo.oauth.client-id}") String clientId,
@Value("${demo.oauth.client-secret}") String clientSecret) {
Assert.hasText(clientId, "demo.oauth.client-id must not be blank");
Assert.hasText(clientSecret, "demo.oauth.client-secret must not be blank");

PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId(clientId)
.clientSecret(passwordEncoder.encode(clientSecret))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.scope("mcp.access")
.clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())
.build();

return new InMemoryRegisteredClientRepository(registeredClient);
}

关键点解读:

  • 客户端 ID 与密钥分别通过 ${demo.oauth.client-id}、${demo.oauth.client-secret} 配置项注入,默认客户端 ID 为 mcp-client,密钥来自环境变量 OAUTH_CLIENT_SECRET(对应 README 中的 export/$env 设置);
  • 客户端密钥使用 PasswordEncoderFactories.createDelegatingPasswordEncoder() 加密后存储,不会以明文落库;
  • 认证方式为 CLIENT_SECRET_BASIC(即 curl -u client:secret 的 HTTP Basic 认证);
  • 授权类型限定为 CLIENT_CREDENTIALS,这是机器与机器(M2M)通信的标准流程,无需用户授权,因此 requireAuthorizationConsent(false)。

5.4 单元测试印证

SecurityConfigurationTest.java 用两条测试验证了配置的健壮性:当 clientId 为空白或 clientSecret 为空字符串时,registeredClientRepository(…) 会抛出 IllegalArgumentException。这说明「密钥必须显式提供」是被测试锁定的硬性约束,也是防止误用默认密钥的第一道防线。

5.5 依赖与版本约束

从 pom.xml 可确认运行前提:Java 17 + Spring Boot 3.2.5,且 spring-boot-starter-oauth2-authorization-server 与 spring-boot-starter-oauth2-resource-server 必须同时存在,缺一不可——前者提供签发能力,后者提供校验能力。

六、容器化构建与本地运行

仓库自带的 Dockerfile 采用多阶段构建:

  • 构建阶段:基于 maven:3.9-eclipse-temurin-17,执行 mvn -ntp -B clean package -DskipTests 产出可执行 JAR;
  • 运行阶段:基于 eclipse-temurin:17-jammy,只拷贝 JAR,暴露 8081 端口,以 java -jar 启动。
  • 本地构建并运行容器:

    docker build -t mcp-oauth2-demo .
    docker run –rm -p 8081:8081 \\
    -e OAUTH_CLIENT_SECRET="$OAUTH_CLIENT_SECRET" \\
    mcp-oauth2-demo

    注意:运行时通过 -e 注入 OAUTH_CLIENT_SECRET,不要把它写进镜像层或 Dockerfile。

    七、生产安全:从 Demo 到生产必须做的四件事

    README 明确强调,生产部署应当用专用身份提供方(IdP)替代这个进程内演示授权服务器,并至少做到:

  • 凭据入托管密钥库:把客户端密钥存放在托管密钥存储中,定期轮换;
  • 签名密钥持久化:使用持久化的签名密钥,而不是启动时临时生成;
  • 收敛作用域:严格限制令牌携带的 scope 权限范围;
  • 显式设置 issuer:为令牌声明显式的签发者(issuer)地址。
  • 此外,绝不要把客户端密钥放进源代码、容器镜像、部署清单或命令行输出中。

    针对 Azure Container Apps 场景,建议把密钥值存为 Container Apps 机密(secret),尽可能由 Key Vault 背书,然后仅通过 OAUTH_CLIENT_SECRET 环境变量的方式暴露机密引用,而不是明文值。

    八、部署到 Azure Container Apps

    使用 Azure CLI 一条命令即可完成部署:

    az containerapp up -n mcp-oauth2 \\
    -g demo-rg -l westeurope \\
    –image <your-registry>/mcp-oauth2-demo:latest \\
    –ingress external –target-port 8081

    部署完成后两个关键事实:

    • 入口(ingress)的 FQDN 就是你的 issuer(签发者):https://<fqdn>;
    • Azure 会为 *.azurecontainerapps.io 域名自动签发可信 TLS 证书,这保证了令牌端点与 JWKS 端点都走 HTTPS,是 APIM 网关验签的前提。

    更完整的部署示例(含查询 FQDN 的 –query 参数)可参考仓库中的 apimoauth.md。部署后,应用对外暴露三类端点:

    端点作用
    https://<fqdn>/oauth2/token 令牌端点,客户端在此获取令牌(client_credentials 流程)
    https://<fqdn>/oauth2/jwks JWKS 端点,返回签名公钥集合
    https://<fqdn>/.well-known/openid-configuration OIDC 发现文档,包含 issuer、token_endpoint、jwks_uri 等元数据

    九、接入 Azure API Management(APIM)

    9.1 添加 validate-jwt 入站策略

    在 APIM 的 API 上添加如下入站策略,让网关在转发前统一校验 JWT:

    <inbound>
    <validate-jwt header-name="Authorization">
    <openid-config url="https://<fqdn>/.well-known/openid-configuration"/>
    <audiences>
    <audience>mcp-client</audience>
    </audiences>
    </validate-jwt>
    <base/>
    </inbound>

    APIM 会自动抓取 JWKS 并对每个请求验签,无需手工维护公钥。

    9.2 三个让 APIM 验签成功的先决条件

    结合 apimoauth.md 的排障清单,务必确认:

  • 启用 OIDC 发现:Spring Authorization Server 默认不暴露 /.well-known/openid-configuration,需要在安全配置中加入 .oidc(Customizer.withDefaults()),否则 APIM 的 <openid-config> 拉取元数据会得到 404;
  • audience 与令牌匹配:若 APIM 的 <audience> 校验失败,需要自定义 JWT 的 aud 声明(例如通过 OAuth2TokenCustomizer 把 audience 设为 mcp-client),使令牌中的 aud 与策略中的 <audience> 一致;
  • HTTPS 与可达性:APIM 网关要求 OpenID/JWKS 端点必须走 HTTPS 且证书可信;同时 Spring 应用的端点必须能从 APIM 访问(–ingress external 是最简单的测试选择)。
  • 9.3 常见陷阱速查

    陷阱现象对策
    未启用 OIDC <openid-config> 返回 404 安全配置加 .oidc(…)
    audience 不匹配 APIM 拒绝合法令牌 定制 token 的 aud 或调整 <audience>
    自定义域名无证书 APIM 拉取元数据失败 绑定免费托管证书或使用默认域名
    策略顺序错误 请求绕过校验直达后端 <validate-jwt> 置于 <inbound> 下、路由之前

    策略说明:<validate-jwt> 应放在 <inbound> 块内且位于任何后端路由之前;令牌经过 APIM 验证后,原 Authorization 头会继续转发给后端。虽然 Spring 资源服务器还会再校验一次,但保留双层校验更安全。

    十、下一步

    本 Demo 属于高级主题章节「05-AdvancedTopics」的组成部分。完成 OAuth2 集成后,可以继续学习根上下文(Root Contexts)机制:5.4 根上下文。在动手实践时请牢记:先本地用 curl 验证令牌闭环,再容器化部署,最后接入 APIM——每一步都以「密钥不落代码、签名密钥持久化、显式 issuer、作用域最小化」为安全底线。

    赞

    分享

    • 教程
    • 文档
    • 人工智能

    【免费下载链接】mcp-for-beginners

    This open-source curriculum introduces the fundamentals of Model Context Protocol (MCP) through real-world, cross-language examples in .NET, Java, TypeScript, JavaScript, Rust and Python. Designed for developers, it focuses on practical techniques for building modular, scalable, and secure AI workflows from session setup to service orchestration.

    项目地址:
    https://gitcode.com/GitHub_Trending/mc/mcp-for-beginners

    点击查看 免费下载

    上一篇:
    模型部署实战指南:Interview-for-Algorithm-Engineer中的vLLM、SGLang等框架实战解析

    下一篇:
    桌面宠物RunCat运行异常排查与修复指南:让可爱猫咪重回任务栏

    创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 为 MCP 服务器接入 OAuth2:基于 Spring Authorization Server 与 Azure API Management 的完整实战(mcp-for-beginners)
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!