在网关后面部署受保护的 API

将受Microsoft.Identity.Web保护的ASP.NET Core Web API部署在Azure API网关和反向代理之后,包括Azure API 管理(APIM)、Azure Front Door和Azure 应用程序网关。

了解网关要求

在网关后面部署受保护的 API 时,必须处理以下几个问题:

  • 转发标头 - 保留原始请求上下文(方案、主机、IP)
  • 令牌验证 - 确保访问群体声明与网关 URL 匹配
  • CORS 配置 - 正确处理跨域请求
  • 健康检查终点 - 提供未经身份验证的健康检查
  • 基于路径的路由 - 支持网关级路径前缀
  • SSL/TLS 终止 - 网关终止 SSL 时正确处理 HTTPS

查看常见网关场景

根据要求选择网关。 以下各节介绍受保护 API 的最常见Azure网关服务。

Azure API 管理 (APIM)

用例: 具有策略、速率限制、转换的企业 API 网关

体系结构:

Client → Microsoft Entra ID → Token
Client → APIM (apim.azure-api.net) → Backend API (app.azurewebsites.net)

关键注意事项:

  • 在转发到后端之前,APIM 策略可以验证 JWT 令牌
  • 后端 API 仍验证令牌
  • 访问群体声明必须与 APIM URL 或后端 URL 匹配(相应地进行配置)

Azure Front Door

用例: 全局负载均衡、CDN、DDoS 保护

体系结构:

Client → Microsoft Entra ID → Token
Client → Front Door (azurefd.net) → Backend API (regional endpoints)

关键注意事项:

  • Front Door 使用标头转发请求X-Forwarded-*
  • Front Door 上的 SSL/TLS 终止
  • 令牌受众验证需要配置

Azure 应用程序网关

用例: 区域负载均衡、WAF、基于路径的路由

体系结构:

Client → Microsoft Entra ID → Token
Client → Application Gateway → Backend API (multiple instances)

关键注意事项:

  • Web 应用程序防火墙 (WAF) 集成
  • 基于路径的路由规则
  • 后端健康探测需要使用未经身份验证的端点

配置常见模式

应用这些配置模式,以确保受保护的 API 在任何网关后面正常工作。

1. 转发标头中间件

始终在网关后面配置转发标头中间件。 以下代码注册中间件并将其设置为在身份验证之前运行:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Configure forwarded headers BEFORE authentication
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto |
                                ForwardedHeaders.XForwardedHost;

    // Clear known networks/proxies to accept forwarded headers from any source
    // (Azure infrastructure will be the proxy)
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Limit to specific headers if needed
    options.ForwardedForHeaderName = "X-Forwarded-For";
    options.ForwardedProtoHeaderName = "X-Forwarded-Proto";
    options.ForwardedHostHeaderName = "X-Forwarded-Host";
});

// Add authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

var app = builder.Build();

// USE forwarded headers BEFORE authentication middleware
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();

app.Run();

转发标头中间件至关重要,因为它:

  • 保留用于日志记录的原始客户端 IP 地址
  • HttpContext.Request.Scheme确保保持原始 HTTPS 协议
  • 为重定向 URL 和令牌验证提供正确的 Host 标头

2. 令牌受众配置

选项 A:接受网关和后端 URL

在你的 appsettings.json 配置中添加多个有效的受众:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-client-id",
    "Audience": "api://your-client-id",
    "TokenValidationParameters": {
      "ValidAudiences": [
        "api://your-client-id",
        "https://your-backend.azurewebsites.net",
        "https://your-apim.azure-api.net"
      ]
    }
  }
}

或者,在Program.cs中以编程方式配置多个受众。

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();

// Customize token validation to accept multiple audiences
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    var existingValidation = options.TokenValidationParameters.AudienceValidator;

    options.TokenValidationParameters.AudienceValidator = (audiences, token, parameters) =>
    {
        var validAudiences = new[]
        {
            "api://your-client-id",
            "https://your-backend.azurewebsites.net",
            "https://your-apim.azure-api.net",
            builder.Configuration["AzureAd:ClientId"] // Also accept ClientId
        };

        return audiences.Any(a => validAudiences.Contains(a, StringComparer.OrdinalIgnoreCase));
    };
});

选项 B:在 APIM 策略中重写目标用户

配置 APIM 以在转发到后端之前验证访问群体声明:

<policies>
    <inbound>
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401">
            <openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://your-client-id</audience>
            </audiences>
        </validate-jwt>

        <!-- Optionally modify token claims for backend -->
        <set-header name="X-Gateway-Validated" exists-action="override">
            <value>true</value>
        </set-header>
    </inbound>
</policies>

3. 健康检查终端配置

网关需要未经身份验证的运行状况终结点进行检测。 在身份验证中间件之前映射健康检查端点以绕过令牌验证。

var app = builder.Build();

// Health endpoint BEFORE authentication middleware
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }))
    .AllowAnonymous();

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

// Protected endpoints require authentication
app.MapControllers();

app.Run();

或者,使用内置 ASP.NET Core健康检查框架进行更丰富的健康报告:

using Microsoft.Extensions.Diagnostics.HealthChecks;

builder.Services.AddHealthChecks()
    .AddCheck("api", () => HealthCheckResult.Healthy());

var app = builder.Build();

app.MapHealthChecks("/health").AllowAnonymous();
app.MapHealthChecks("/ready").AllowAnonymous();

app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

4. 网关后面的 CORS 配置

将 Azure Front Door 或 APIM 用于前端应用程序时,请将 CORS 配置为允许来自网关源的请求:

builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowGateway", policy =>
    {
        policy.WithOrigins(
            "https://your-apim.azure-api.net",
            "https://your-frontend.azurefd.net",
            "https://your-app.azurewebsites.net"
        )
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials(); // If using cookies
    });
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseCors("AllowGateway");
app.UseAuthentication();
app.UseAuthorization();

app.Run();

重要

必须在转发标头 之后 和身份验证 之前 配置 CORS。


与Azure API 管理集成

本部分提供用于在 Azure API 管理 后面部署受保护 API 的完整配置。

配置后端 API

Program.cs 中设置转发标头和Microsoft Entra ID身份验证:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Forwarded headers for APIM
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.All;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddControllers();

var app = builder.Build();

// Middleware order matters
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

将 Microsoft Entra 配置添加到 appsettings.json

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-backend-api-client-id",
    "Audience": "api://your-backend-api-client-id"
  }
}

为 JWT 验证添加 APIM 入站策略

定义一个入站策略,用于验证 JWT 令牌、应用速率限制并将请求转发到后端:

<policies>
    <inbound>
        <base />

        <!-- Validate JWT token -->
        <validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
            <openid-config url="https://login.microsoftonline.com/{your-tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://your-backend-api-client-id</audience>
            </audiences>
            <issuers>
                <issuer>https://login.microsoftonline.com/{your-tenant-id}/v2.0</issuer>
            </issuers>
            <required-claims>
                <claim name="scp" match="any">
                    <value>access_as_user</value>
                </claim>
            </required-claims>
        </validate-jwt>

        <!-- Rate limiting -->
        <rate-limit calls="100" renewal-period="60" />

        <!-- Forward original host header -->
        <set-header name="X-Forwarded-Host" exists-action="override">
            <value>@(context.Request.OriginalUrl.Host)</value>
        </set-header>

        <!-- Forward to backend -->
        <set-backend-service base-url="https://your-backend.azurewebsites.net" />
    </inbound>

    <backend>
        <base />
    </backend>

    <outbound>
        <base />
    </outbound>

    <on-error>
        <base />
    </on-error>
</policies>

配置 APIM API 设置

使用以下命名值和 API 设置来完成 APIM 配置:

命名值(为可重用性):

  • tenant-id:您的 Microsoft Entra 租户 ID
  • backend-api-client-id:后端 API 的客户端 ID
  • backend-base-url: https://your-backend.azurewebsites.net

API 设置:

  • API URL 后缀/api (可选路径前缀)
  • Web 服务 URL:通过策略使用命名值进行设置
  • 需要订阅:是(添加另一层安全性)

配置客户端应用程序

客户端应用请求 后端 API 的令牌,而不是 APIM。 以下代码获取令牌并通过 APIM 终结点调用 API:

// Client app requests token
var result = await app.AcquireTokenSilent(
    scopes: new[] { "api://your-backend-api-client-id/access_as_user" },
    account)
    .ExecuteAsync();

// Call APIM URL with token
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", result.AccessToken);

// Add APIM subscription key
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", "your-subscription-key");

var response = await client.GetAsync("https://your-apim.azure-api.net/api/weatherforecast");

与 Azure Front Door 集成

配置受保护的 API,通过 Azure Front Door 实现全球分发。

配置后端 API

Program.cs 中为Azure Front Door设置转发标头:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Configure for Azure Front Door
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto |
                                ForwardedHeaders.XForwardedHost;

    // Accept headers from any source (Azure Front Door)
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Front Door specific headers
    options.ForwardedForHeaderName = "X-Forwarded-For";
    options.ForwardedProtoHeaderName = "X-Forwarded-Proto";
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

var app = builder.Build();

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

app.MapControllers();

app.Run();

配置 Front Door 原始位置

在 Azure 门户中完成以下步骤以设置 Azure Front Door 源:

  1. 创建 Front Door 配置文件
  2. 添加带有后端 API 实例的源服务器组
  3. 将运行状况探测配置到 /health 终结点
  4. 设置仅 HTTPS 转发
  5. 启用 WAF 策略(可选)

健康探测设置:

  • 路径/health
  • 协议:HTTPS
  • 方法:GET
  • 间隔:30 秒

处理多个区域

当你在 Front Door 背后的多个地区进行部署时,请增加日志记录和诊断的区域识别功能:

// Add region awareness for logging/diagnostics
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

app.Use(async (context, next) =>
{
    // Log the actual client IP and region
    var clientIp = context.Connection.RemoteIpAddress?.ToString();
    var forwardedFor = context.Request.Headers["X-Forwarded-For"].ToString();
    var frontDoorId = context.Request.Headers["X-Azure-FDID"].ToString();

    // Add to logger scope or response headers
    context.Response.Headers.Add("X-Served-By-Region",
        builder.Configuration["Region"] ?? "unknown");

    await next();
});

使用 Front Door 验证令牌

如果客户端请求将令牌限定为 Front Door URL,请将其添加到有效受众列表中:

builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters.ValidAudiences = new[]
    {
        "api://your-backend-api-client-id",
        "https://your-frontend.azurefd.net", // Front Door URL
        builder.Configuration["AzureAd:ClientId"]
    };
});

与 Azure 应用程序网关集成

使用 Web 应用程序防火墙 (WAF) 支持在 Azure 应用程序网关 后面配置受保护的 API。

配置后端 API

Program.cs中为应用程序网关设置转发标头:

using Microsoft.AspNetCore.HttpOverrides;

var builder = WebApplication.CreateBuilder(args);

// Application Gateway uses standard forwarded headers
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor |
                                ForwardedHeaders.XForwardedProto;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddHealthChecks();

var app = builder.Build();

// Health endpoint for Application Gateway probes
app.MapHealthChecks("/health").AllowAnonymous();

app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

配置应用程序网关设置

在Azure门户中设置以下后端、运行状况探测和 WAF 设置:

后端设置:

  • 协议:HTTPS(建议)或 HTTP
  • 端口:443 或 80
  • 替代后端路径:否(除非需要)
  • 自定义探测:是,指向 /health

健康探测:

  • 协议:HTTPS 或 HTTP
  • 主机:保留默认值或指定
  • 路径/health
  • 间隔:30 秒
  • 不正常阈值:3

WAF 策略:

  • 使用 OWASP 3.2 规则集启用 WAF
  • 重要说明:确保不会阻止标头中的 Authorization JWT 令牌
  • 可能需要为包含“授权”的 RequestHeaderNames 创建 WAF 规则排除。

设置基于路径的路由

使用基于路径的路由规则时,请将后端 API 配置为处理路径前缀:

// Backend API should work regardless of path prefix
var app = builder.Build();

// Option 1: Use path base (if gateway adds prefix)
app.UsePathBase("/api/v1");

// Option 2: Configure routing explicitly
app.UseForwardedHeaders();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

应用程序网关规则:

  • 路径/api/v1/*
  • 后端目标:你的后端池
  • 后端设置:使用已配置设置

排查常见问题

使用这些解决方案解决在网关后面部署受保护的 API 时最常见的问题。

问题:部署在网关后出现401未授权

症状:

  • API 在本地环境下正常运行,但在网关后返回 401
  • 在 jwt.ms 解码时令牌似乎有效

可能的原因:

  1. 受众声明不匹配

    # Check token audience
    # Decode token and verify 'aud' claim matches one of:
    # - api://your-client-id
    # - https://your-backend.azurewebsites.net
    # - https://your-gateway-url
    
  2. 缺少转发标头中间件

    // Ensure this is BEFORE authentication
    app.UseForwardedHeaders();
    app.UseAuthentication();
    
  3. HTTPS 重定向问题

    // If gateway terminates SSL, may need to disable or configure carefully
    if (!app.Environment.IsDevelopment())
    {
        app.UseHttpsRedirection();
    }
    

Solution:

  • 启用调试日志记录以查看令牌验证详细信息
  • 在令牌验证中添加多个有效受众
  • 请验证X-Forwarded-*标头是否由网关转发

问题:健康探测失败

症状:

  • 网关将后端标记为不正常
  • 运行状况终结点返回 401

Solution:

确保健康检查端点在身份验证中间件之前运行。

// Ensure health endpoint is BEFORE authentication
app.MapHealthChecks("/health").AllowAnonymous();

// Alternative: Use custom middleware
app.Map("/health", healthApp =>
{
    healthApp.Run(async context =>
    {
        context.Response.StatusCode = 200;
        await context.Response.WriteAsync("healthy");
    });
});

app.UseAuthentication(); // Health endpoint bypasses this

问题:Azure Front Door 后的 CORS 错误

症状:

  • 预检 OPTIONS 请求失败
  • 浏览器控制台显示 CORS 错误

Solution:

将 Front Door 和前端源添加到 CORS 策略:

builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.WithOrigins(
            "https://your-frontend.azurefd.net",
            "https://your-app.com"
        )
        .AllowAnyMethod()
        .AllowAnyHeader()
        .AllowCredentials();
    });
});

var app = builder.Build();

app.UseForwardedHeaders();
app.UseCors(); // Before authentication
app.UseAuthentication();
app.UseAuthorization();

问题:日志中的“转发标头”警告

症状:

Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware: Unknown proxy

Solution:

清除已知网络和代理以接受来自Azure基础结构的转发标头:

builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    // Clear known networks to accept from any proxy
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();

    // Or explicitly add Azure IP ranges (more secure but complex)
    // options.KnownProxies.Add(IPAddress.Parse("20.x.x.x"));
});

问题:APIM 返回 401,但后端返回 200

症状:

  • 令牌对后端有效
  • APIM validate-jwt 策略失败

Solution:

验证 APIM 策略访问群体是否与令牌访问群体匹配:

<validate-jwt header-name="Authorization">
    <openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
    <audiences>
        <!-- Must match the 'aud' claim in your token -->
        <audience>api://your-backend-api-client-id</audience>
    </audiences>
</validate-jwt>

问题:多个身份验证方案冲突

症状:

  • 同时使用 JWT 承载者和其他身份验证方案
  • 已选择错误的方案

Solution:

在控制器中显式指定身份验证方案:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .AddScheme<MyCustomOptions, MyCustomHandler>("CustomScheme", options => {});

// In controller, specify scheme explicitly
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class WeatherForecastController : ControllerBase
{
    // ...
}

遵循最佳做法

应用这些做法,在网关后面构建安全且可复原的 API 部署。

1. 深度防御

始终在后端 API 中验证令牌,即使网关已经验证了它们。

// Gateway validates token (APIM policy)
// Backend ALSO validates token (Microsoft.Identity.Web)
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

网关配置允许更改,令牌可能被重放。 深度防御对于安全性至关重要。

2.使用托管标识进行网关到后端通信

如果网关使用自己的标识调用后端,请将后端配置为接受用户令牌和托管标识令牌:

// Backend accepts both user tokens and gateway's managed identity
builder.Services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
{
    options.TokenValidationParameters.ValidAudiences = new[]
    {
        "api://backend-api-client-id", // User tokens
        "https://management.azure.com" // Managed identity tokens (if applicable)
    };
});

3. 监视网关指标

跟踪这些关键指标,以保持对网关部署的可见性:

  • 401/403 错误率
  • 令牌验证失败
  • 健康探测器故障
  • 转发标头(用于调试)

4. 使用 Application Insights

将 Application Insights 遥测添加到记录特定于网关的请求属性中:

builder.Services.AddApplicationInsightsTelemetry();

// Log custom properties
app.Use(async (context, next) =>
{
    var telemetry = context.RequestServices.GetRequiredService<TelemetryClient>();
    telemetry.TrackEvent("ApiRequest", new Dictionary<string, string>
    {
        ["ForwardedFor"] = context.Request.Headers["X-Forwarded-For"],
        ["OriginalHost"] = context.Request.Headers["X-Forwarded-Host"],
        ["Gateway"] = "APIM" // or "FrontDoor", "AppGateway"
    });

    await next();
});

5. 将健康状态与就绪状态区分开

使用不同的端点进行存活性(服务是否正在运行?)和就绪性(服务是否可以接受流量?)检查:

// Health: Is the service running?
app.MapGet("/health", () => Results.Ok()).AllowAnonymous();

// Ready: Can the service accept traffic?
app.MapHealthChecks("/ready", new HealthCheckOptions
{
    Predicate = check => check.Tags.Contains("ready")
}).AllowAnonymous();

builder.Services.AddHealthChecks()
    .AddCheck("database", () => /* check DB */ , tags: new[] { "ready" })
    .AddCheck("cache", () => /* check cache */ , tags: new[] { "ready" });

6.记录网关配置

创建 README 或 Wiki 页面,记录以下内容:

  • 正在使用哪个网关
  • 令牌用户期望
  • CORS 配置
  • 运行状况探测端点
  • 转发标头配置
  • 紧急回滚程序

使用 Azure API 管理 生成完整的示例

本部分提供了一个在Azure API 管理后面运行,并使用Microsoft Entra ID进行身份验证的ASP.NET Core API的完整生产就绪示例。

后端 API (ASP.NET Core)

以下 Program.cs配置转发标头、Microsoft Entra身份验证、运行状况检查和 Application Insights:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Forwarded headers for APIM
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.All;
    options.KnownNetworks.Clear();
    options.KnownProxies.Clear();
});

// Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddMicrosoftGraph()
    .AddInMemoryTokenCaches();

// Application Insights
builder.Services.AddApplicationInsightsTelemetry();

// Health checks
builder.Services.AddHealthChecks();

builder.Services.AddControllers();

var app = builder.Build();

// Health endpoint (unauthenticated)
app.MapHealthChecks("/health").AllowAnonymous();

// Middleware order is critical
app.UseForwardedHeaders();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

将以下 Microsoft Entra 和 Application Insights 配置添加到 appsettings.json

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "backend-api-client-id",
    "Audience": "api://backend-api-client-id"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning",
      "Microsoft.Identity.Web": "Debug"
    }
  },
  "ApplicationInsights": {
    "ConnectionString": "your-connection-string"
  }
}

以下控制器需要身份验证,并记录转发的标头以用于调试:

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;

[Authorize]
[ApiController]
[Route("[controller]")]
[RequiredScope("access_as_user")]
public class WeatherForecastController : ControllerBase
{
    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IActionResult Get()
    {
        // Log forwarded headers for debugging
        var forwardedFor = HttpContext.Request.Headers["X-Forwarded-For"];
        var forwardedHost = HttpContext.Request.Headers["X-Forwarded-Host"];

        _logger.LogInformation(
            "Request from {ForwardedFor} via {ForwardedHost}",
            forwardedFor,
            forwardedHost);

        return Ok(new[] { "Weather", "Forecast", "Data" });
    }
}

APIM 配置

以下入站策略验证 JWT 令牌、应用速率限制、转发标头和配置 CORS:

<policies>
    <inbound>
        <base />

        <!-- Rate limiting per subscription -->
        <rate-limit-by-key calls="100" renewal-period="60"
                           counter-key="@(context.Subscription.Id)" />

        <!-- Validate JWT -->
        <validate-jwt header-name="Authorization"
                      failed-validation-httpcode="401"
                      failed-validation-error-message="Unauthorized">
            <openid-config url="https://login.microsoftonline.com/{tenant-id}/v2.0/.well-known/openid-configuration" />
            <audiences>
                <audience>api://backend-api-client-id</audience>
            </audiences>
            <issuers>
                <issuer>https://login.microsoftonline.com/{tenant-id}/v2.0</issuer>
            </issuers>
            <required-claims>
                <claim name="scp" match="any">
                    <value>access_as_user</value>
                </claim>
            </required-claims>
        </validate-jwt>

        <!-- Forward headers -->
        <set-header name="X-Forwarded-Host" exists-action="override">
            <value>@(context.Request.OriginalUrl.Host)</value>
        </set-header>
        <set-header name="X-Forwarded-Proto" exists-action="override">
            <value>@(context.Request.OriginalUrl.Scheme)</value>
        </set-header>

        <!-- Backend URL -->
        <set-backend-service base-url="https://your-backend.azurewebsites.net" />
    </inbound>

    <backend>
        <base />
    </backend>

    <outbound>
        <base />

        <!-- Add CORS headers if needed -->
        <cors>
            <allowed-origins>
                <origin>https://your-frontend.com</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
        </cors>
    </outbound>

    <on-error>
        <base />
    </on-error>
</policies>