使用OAuth和Entra ID实现安全的MCP服务器

本文详细介绍了如何使用ASP.NET Core和Microsoft Entra ID实现安全的模型上下文协议(MCP)服务器,包含OAuth认证、授权策略配置、MCP客户端实现以及与Azure OpenAI集成的完整技术架构。

实现安全的MCP服务器

本文展示了如何使用OAuth和Entra ID实现安全的模型上下文协议(MCP)服务器。MCP服务器使用ASP.NET Core实现,并使用Microsoft Entra ID来保护API。使用Azure OpenAI和语义内核的ASP.NET Core应用程序用于实现代理访问的MCP客户端。

代码库:https://github.com/damienbod/McpSecurity

设置

解决方案在两个独立的ASP.NET Core服务中实现,并使用Azure OpenAI来实现代理和LLM处理。MCP服务器使用ModelContextProtocol.AspNetCore Nuget包与Microsoft.Identity.Web一起实现,以保护API(MCP服务器)。MCP客户端可以在ASP.NET Core Web应用程序中实现和使用。该客户端使用Microsoft Entra ID和Microsoft.Identity.Web Nuget包进行身份验证。客户端使用Azure OpenAI来处理提示。

实现安全的MCP服务器

使用ModelContextProtocol.AspNetCore Nuget包在.NET中设置MCP服务器相当简单。这提供了扩展方法,您只需要定义MCP服务器提供的MCP工具。MCP服务器是一个API,可以使用来自Microsoft Entra ID的委托访问令牌进行保护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration);
var httpMcpServerUrl = builder.Configuration["HttpMcpServerUrl"];

builder.Services.AddAuthentication().AddMcp(options =>
{
    options.ResourceMetadata = new()
    {
        Resource = new Uri(httpMcpServerUrl),
        ResourceDocumentation = new Uri("https://mcpoauthsecurity-hag0drckepathyb6.westeurope-01.azurewebsites.net/health"),
        ScopesSupported = ["mcp:tools"],
    };
});

builder.Services.AddAuthorization();

builder.Services
       .AddMcpServer()
       .WithHttpTransport()
       .WithTools<RandomNumberTools>()
       .WithTools<DateTools>()
       .WithTools<WeatherTools>();

// 添加CORS以支持浏览器中的HTTP传输
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyHeader()
              .AllowAnyMethod();
    });
});

builder.Services.AddHttpClient();

// 更改作用域验证以确保仅使用委托访问令牌
builder.Services.AddAuthorizationBuilder()
  .AddPolicy("mcp_tools", policy =>
        policy.RequireClaim("http://schemas.microsoft.com/identity/claims/scope", "mcp:tools"));

身份验证和授权中间件可以添加到MCP端点,并要求具有所需scp声明和值的有效Microsoft Entra ID令牌才能使用此服务。“mcp_tools"策略用于强制执行此操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 配置HTTP请求管道
app.UseHttpsRedirection();

// 启用CORS
app.UseCors();

app.MapGet("/health", () => $"Secure MCP server running deployed: UTC: {DateTime.UtcNow}, use /mcp path to use the tools");

app.UseAuthentication();
app.UseAuthorization();

app.MapMcp("/mcp").RequireAuthorization("mcp_tools");

在ASP.NET Core中实现MCP客户端

现在我们有了安全的MCP服务器,我们可以创建一个客户端来使用API。我使用ASP.NET Core和Azure OpenAI实现了第一个客户端。ASP.NET Core应用程序使用OpenID Connect代码流与PKCE进行保护。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi(["api://96b0f495-3b65-4c8f-a0c6-c3767c3365ed/mcp:tools"])
    .AddInMemoryTokenCaches();

builder.Services.AddAuthorization(options =>
{
    // 默认情况下,所有传入请求将根据默认策略进行授权
    options.FallbackPolicy = options.DefaultPolicy;
});

builder.Services.AddRazorPages()
    .AddMicrosoftIdentityUI();

builder.Services.AddScoped<ChatService>();

var app = builder.Build();

// 配置HTTP请求管道
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();

app.MapStaticAssets();
app.MapRazorPages()
   .WithStaticAssets();
app.MapControllers();

app.Run();

ChatService

使用Microsoft.SemanticKernel Nuget包通过Azure OpenAI实现代理逻辑。使用HttpClient访问MCP服务器。委托访问令牌可以用作Bearer令牌,MCP服务器功能可以在代理逻辑中调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class ChatService
{
    private readonly IConfiguration _configuration;
    private readonly ElicitationCoordinator _elicitationCoordinator;
    private Kernel _kernel;
    private IMcpClient _mcpClient = null!;
    private bool _initialized;
    private ApprovalMode _mode = ApprovalMode.Manual;
    private readonly ITokenAcquisition _tokenAcquisition;
    
    private PromptingService? _promptingService;
    
    public ChatService(IConfiguration configuration, ElicitationCoordinator elicitationCoordinator, ITokenAcquisition tokenAcquisition)
    {
        _configuration = configuration;
        _elicitationCoordinator = elicitationCoordinator;
        var config = new ConfigurationBuilder()
            .AddUserSecrets<Program>()
            .Build();
        _kernel = SemanticKernelHelper.GetKernel(config);
        _tokenAcquisition = tokenAcquisition;
    }
    
    public async Task EnsureSetupAsync(IHttpClientFactory clientFactory)
    {
        if (_initialized) return;
        
        var accessToken = await _tokenAcquisition
            .GetAccessTokenForUserAsync([_configuration["McpScope"]!]);
        
        _mcpClient = await McpClientFactory.CreateAsync(CreateMcpTransport(clientFactory, accessToken), GetMcpOptions());
        await _kernel.ImportMcpClientToolsAsync(_mcpClient);
        
        _promptingService = new PromptingService(_kernel, autoInvoke: _mode == ApprovalMode.Elicitation);
        _initialized = true;
    }
    
    private IClientTransport CreateMcpTransport(IHttpClientFactory clientFactory, string accessToken)
    {
        var httpClient = clientFactory.CreateClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
        var httpMcpServerUrl = _configuration["HttpMcpServerUrl"] ?? throw new ArgumentNullException("Configuration missing for HttpMcpServerUrl");
        return new SseClientTransport(new() { Endpoint = new Uri(httpMcpServerUrl), Name = "Secure Client" }, httpClient);
    }
}

可以从UI发送提示,并使用MCP服务器。我使用了一个简单的Razor页面应用程序来处理UI逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public async Task<IActionResult> OnPostAsync()
{
    if (!ModelState.IsValid)
    {
        return OnGet();
    }
    
    _chatService.SetMode(SelectedMode);
    await _chatService.EnsureSetupAsync(_clientFactory);
    
    // 使用提示开始新的聊天
    var response = await _chatService.BeginChatAsync(GetUserKey(), Prompt);
    PromptResults = response.FinalAnswer;
    PendingFunctions = response.PendingFunctions;
    return Page();
}

注意事项

这种方法运行稳定,MCP服务器的作用域限制为用户身份,即委托访问令牌。Entra ID适用于没有云限制的企业类型解决方案。使用提供的Nuget包可以轻松实现Azure OpenAI。未使用OAuth DCR。在MCP服务器中验证作用域以防止客户端使用应用程序身份非常重要。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计