保护 AI 工作流:为 Model Context Protocol 服务器使用 Entra ID 认证 介绍 保护你的 Model Context Protocol(MCP)服务器就像锁好家门一样重要。若让 MCP 服务器处于开放状态,可能会导致工具和数据被未经授权访问,带来安全隐患。Microsoft Entra ID 提供了强大的云端身份和访问管理解决方案,帮助确保只有授权的用户和应用程序可以访问你的 MCP 服务器。本节将教你如何通过 Entra ID 认证来保护你的 AI 工作流。 学习目标 完成本节后,你将能够: 理解保护 MCP 服务器的重要性。 解释 Microsoft Entra ID 和 OAuth 2.0 认证的基础知识。 识别公共客户端和机密客户端的区别。
保护你的 Model Context Protocol(MCP)服务器就像锁好家门一样重要。若让 MCP 服务器处于开放状态,可能会导致工具和数据被未经授权访问,带来安全隐患。Microsoft Entra ID 提供了强大的云端身份和访问管理解决方案,帮助确保只有授权的用户和应用程序可以访问你的 MCP 服务器。本节将教你如何通过 Entra ID 认证来保护你的 AI 工作流。
完成本节后,你将能够:
就像你不会让家门大开一样,也不应让 MCP 服务器随意访问。保护你的 AI 工作流对于构建健壮、可信且安全的应用至关重要。本章将介绍如何使用 Microsoft Entra ID 保护 MCP 服务器,确保只有授权用户和应用能访问你的工具和数据。
想象一下你的 MCP 服务器中有个工具可以发送邮件或访问客户数据库。如果服务器没有安全保护,任何人都可能使用该工具,导致数据泄露、垃圾邮件或其他恶意行为。
通过实施认证,你可以确保每个请求都经过验证,确认发起请求的用户或应用身份。这是保护 AI 工作流的第一步,也是最关键的一步。
Microsoft Entra ID 是一项基于云的身份和访问管理服务。可以把它看作是你应用的通用保安,负责验证用户身份(认证)以及确定他们的权限(授权)。
使用 Entra ID,你可以:
对于 MCP 服务器,Entra ID 提供了一个强大且广泛信赖的解决方案,帮助管理谁能访问服务器的功能。
Entra ID 使用像 OAuth 2.0 这样的开放标准来处理认证。虽然细节可能复杂,但核心概念可以通过一个比喻轻松理解。
把 OAuth 2.0 想象成汽车代客服务。当你到餐厅时,你不会把车的主钥匙交给代客,而是给他一把代客钥匙,这把钥匙权限有限——能启动车辆并锁门,但不能打开后备箱或手套箱。
在这个比喻中:
访问令牌是 MCP 客户端在你登录后从 Entra ID 获得的一串安全字符串。客户端在每次请求 MCP 服务器时都会携带该令牌。服务器通过验证令牌,确认请求是否合法,且客户端是否具备所需权限,而无需直接处理你的真实凭据(如密码)。
实际流程如下:
在深入代码之前,先介绍一个示例中会用到的重要组件:Microsoft Authentication Library (MSAL)。
MSAL 是微软开发的一个库,极大简化了开发者处理认证的工作。你无需编写复杂代码来管理安全令牌、登录流程和会话刷新,MSAL 会帮你完成这些繁重任务。
推荐使用 MSAL 的原因包括:
MSAL 支持多种语言和应用框架,包括 .NET、JavaScript/TypeScript、Python、Java、Go 以及 iOS 和 Android 移动平台,确保你能在整个技术栈中使用一致的认证模式。
想了解更多 MSAL,可以查看官方MSAL 概览文档。
现在,让我们通过示例了解如何保护一个本地 MCP 服务器(通过 stdio) using Entra ID. This example uses a public client, which is suitable for applications running on a user's machine, like a desktop app or a local development server.
In this scenario, we'll look at an MCP server that runs locally, communicates over stdio, and uses Entra ID to authenticate the user before allowing access to its tools. The server will have a single tool that fetches the user's profile information from the Microsoft Graph API.
Before writing any code, you need to register your application in Microsoft Entra ID. This tells Entra ID about your application and grants it permission to use the authentication service.
Once registered, take note of the Application (client) ID and Directory (tenant) ID. You'll need these in your code.
Let's look at the key parts of the code that handle authentication. The full code for this example is available in the Entra ID - Local - WAM folder of the mcp-auth-servers GitHub repository.
AuthenticationService.cs
This class is responsible for handling the interaction with Entra ID.
CreateAsync: This method initializes the PublicClientApplication from the MSAL (Microsoft Authentication Library). It's configured with your application's clientId and tenantId.WithBroker: This enables the use of a broker (like the Windows Web Account Manager), which provides a more secure and seamless single sign-on experience.AcquireTokenAsync:这是核心方法。它会先尝试静默获取令牌(如果已有有效会话,用户无需再次登录)。若静默获取失败,则会提示用户交互式登录。// Simplified for clarity public static async Task<AuthenticationService> CreateAsync(ILogger<AuthenticationService> logger) { var msalClient = PublicClientApplicationBuilder .Create(_clientId) // Your Application (client) ID .WithAuthority(AadAuthorityAudience.AzureAdMyOrg) .WithTenantId(_tenantId) // Your Directory (tenant) ID .WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows)) .Build(); // ... cache registration ... return new AuthenticationService(logger, msalClient); } public async Task<string> AcquireTokenAsync() { try { // Try silent authentication first var accounts = await _msalClient.GetAccountsAsync(); var account = accounts.FirstOrDefault(); AuthenticationResult? result = null; if (account != null) { result = await _msalClient.AcquireTokenSilent(_scopes, account).ExecuteAsync(); } else { // If no account, or silent fails, go interactive result = await _msalClient.AcquireTokenInteractive(_scopes).ExecuteAsync(); } return result.AccessToken; } catch (Exception ex) { _logger.LogError(ex, "An error occurred while acquiring the token."); throw; // Optionally rethrow the exception for higher-level handling } }
Program.cs
This is where the MCP server is set up and the authentication service is integrated.
AddSingleton<AuthenticationService>: This registers the AuthenticationService with the dependency injection container, so it can be used by other parts of the application (like our tool).GetUserDetailsFromGraph tool: This tool requires an instance of AuthenticationService. Before it does anything, it calls authService.AcquireTokenAsync() 用于获取有效访问令牌。认证成功后,使用令牌调用 Microsoft Graph API 获取用户详细信息。// Simplified for clarity [McpServerTool(Name = "GetUserDetailsFromGraph")] public static async Task<string> GetUserDetailsFromGraph( AuthenticationService authService) { try { // This will trigger the authentication flow var accessToken = await authService.AcquireTokenAsync(); // Use the token to create a GraphServiceClient var graphClient = new GraphServiceClient( new BaseBearerTokenAuthenticationProvider(new TokenProvider(authService))); var user = await graphClient.Me.GetAsync(); return System.Text.Json.JsonSerializer.Serialize(user); } catch (Exception ex) { return $"Error: {ex.Message}"; } }
GetUserDetailsFromGraph tool, the tool first calls AcquireTokenAsync.AcquireTokenAsync triggers the MSAL library to check for a valid token.This process ensures that only authenticated users can use the tool, effectively securing your local MCP server.
When your MCP server is running on a remote machine (like a cloud server) and communicates over a protocol like HTTP Streaming, the security requirements are different. In this case, you should use a confidential client and the Authorization Code Flow. This is a more secure method because the application's secrets are never exposed to the browser.
This example uses a TypeScript-based MCP server that uses Express.js to handle HTTP requests.
The setup in Entra ID is similar to the public client, but with one key difference: you need to create a client secret.
http://localhost:3001/auth/callback).⚠️ Important Security Note: For production applications, Microsoft strongly recommends using secretless authentication methods such as Managed Identity or Workload Identity Federation instead of client secrets. Client secrets pose security risks as they can be exposed or compromised. Managed identities provide a more secure approach by eliminating the need to store credentials in your code or configuration.
For more information about managed identities and how to implement them, see the Managed identities for Azure resources overview.
This example uses a session-based approach. When the user authenticates, the server stores the access token and refresh token in a session and gives the user a session token. This session token is then used for subsequent requests. The full code for this example is available in the Entra ID - Confidential client folder of the mcp-auth-servers GitHub repository.
Server.ts
This file sets up the Express server and the MCP transport layer.
requireBearerAuth: This is middleware that protects the /sse and /message endpoints. It checks for a valid bearer token in the Authorization header of the request.EntraIdServerAuthProvider: This is a custom class that implements the McpServerAuthorizationProvider interface. It's responsible for handling the OAuth 2.0 flow./auth/callback:该端点处理用户认证后 Entra ID 的重定向,负责用授权码交换访问令牌和刷新令牌。// Simplified for clarity const app = express(); const { server } = createServer(); const provider = new EntraIdServerAuthProvider(); // Protect the SSE endpoint app.get("/sse", requireBearerAuth({ provider, requiredScopes: ["User.Read"] }), async (req, res) => { // ... connect to the transport ... }); // Protect the message endpoint app.post("/message", requireBearerAuth({ provider, requiredScopes: ["User.Read"] }), async (req, res) => { // ... handle the message ... }); // Handle the OAuth 2.0 callback app.get("/auth/callback", (req, res) => { provider.handleCallback(req.query.code, req.query.state) .then(result => { // ... handle success or failure ... }); });
Tools.ts
This file defines the tools that the MCP server provides. The getUserDetails 工具与前例类似,但从会话中获取访问令牌。
// Simplified for clarity server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name } = request.params; const context = request.params?.context as { token?: string } | undefined; const sessionToken = context?.token; if (name === ToolName.GET_USER_DETAILS) { if (!sessionToken) { throw new AuthenticationError("Authentication token is missing or invalid. Ensure the token is provided in the request context."); } // Get the Entra ID token from the session store const tokenData = tokenStore.getToken(sessionToken); const entraIdToken = tokenData.accessToken; const graphClient = Client.init({ authProvider: (done) => { done(null, entraIdToken); } }); const user = await graphClient.api('/me').get(); // ... return user details ... } });
auth/EntraIdServerAuthProvider.ts
This class handles the logic for:
tokenStore.requireBearerAuth middleware will see that they don't have a valid session and will redirect them to the Entra ID sign-in page./auth/callback endpoint with an authorization code.Authorization header for all future requests to the MCP server.getUserDetails 工具调用时,会用会话令牌查找 Entra ID 访问令牌,再用该令牌调用 Microsoft Graph API。该流程比公共客户端流程更复杂,但针对面向互联网的端点是必须的。远程 MCP 服务器通过公网访问,需要更严格的安全措施防止未授权访问和潜在攻击。
进入 Microsoft Entra 门户。
为你的 MCP 服务器注册一个新应用。
记录应用(客户端)ID 和目录(租户)ID。
MSAL 概览文档
了解 Microsoft Authentication Library (MSAL) 如何实现跨平台的安全令牌获取:
MSAL Overview on Microsoft Learn
Azure-Samples/mcp-auth-servers GitHub 仓库
MCP 服务器认证流程的参考实现:
Azure-Samples/mcp-auth-servers on GitHub
Azure 资源的托管身份概览
了解如何通过系统或用户分配的托管身份消除密钥使用:
Managed Identities Overview on Microsoft Learn
Azure API Management:MCP 服务器的认证网关
深入了解如何使用 APIM 作为 MCP 服务器的安全 OAuth2 网关:
Azure API Management Your Auth Gateway For MCP Servers
Microsoft Graph 权限参考
Microsoft Graph 委托权限和应用权限的完整列表:
Microsoft Graph Permissions Reference
完成本节后,你将能够:
免责声明:
本文件使用 AI 翻译服务 Co-op Translator 进行翻译。虽然我们力求准确,但请注意,自动翻译可能存在错误或不准确之处。原始文件的原文版本应被视为权威来源。对于重要信息,建议使用专业人工翻译。我们不对因使用本翻译而产生的任何误解或误释承担责任。