You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET6:如何根据请求路由通过中间件添加不同安全头?

解决方案:Razor Pages与API端点的差异化安全头配置

问题回顾

应用同时包含Razor Pages和/api开头的API端点,需要实现:

  • 为不同类型端点添加不同安全头
  • 仅为Razor Pages生成CSP随机数(nonce)供内联脚本使用

之前的两种方案存在响应头不生效或HttpContext.Items值无法在Razor视图中获取的问题。

核心问题分析

  • 方案1错误:使用app.Map("/api", ...)和app.MapFallback(...)创建分支管道,但未在分支内添加端点映射(如MapRazorPages),导致请求未正确路由,中间件逻辑未执行。
  • 方案2错误:在Use中间件中直接调用HandleApiRequests(app)/HandleRazorRequests(app),本质是向全局管道重复添加中间件,而非针对当前请求分支处理,导致上下文传递异常。

正确实现方案

1. 修正中间件逻辑(生成随机Nonce)

public class SecurityHeaderOptions
{
    public bool IsApi { get; set; }
}

public class SecurityHeaderMiddleware
{
    private readonly RequestDelegate _next;
    private readonly SecurityHeaderOptions _options;

    public SecurityHeaderMiddleware(RequestDelegate next, SecurityHeaderOptions options)
    {
        _next = next;
        _options = options;
    }

    public async Task InvokeAsync(HttpContext httpContext)
    {
        if (_options.IsApi)
        {
            // API专用安全头配置
            httpContext.Response.Headers["Content-Security-Policy"] = "default-src 'self';";
            await _next(httpContext);
            return;
        }

        // 为Razor Pages生成随机nonce
        var nonce = Guid.NewGuid().ToString("N");
        httpContext.Items["csp-nonce"] = nonce;
        // 配置包含nonce的CSP规则
        httpContext.Response.Headers["Content-Security-Policy"] = $"default-src 'self'; script-src 'self' 'nonce-{nonce}';";

        await _next(httpContext);
    }
}

public static class IApplicationBuilderExtensions
{
    public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app, SecurityHeaderOptions options)
    {
        return app.UseMiddleware<SecurityHeaderMiddleware>(options);
    }
}

2. 正确配置管道分支(Program.cs)

使用MapWhen按路径分支配置中间件,确保上下文正确传递:

var builder = WebApplication.CreateBuilder(args);

// 注册服务
builder.Services.AddRazorPages();
// 如果有API控制器,需注册:builder.Services.AddControllers();

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

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

// 针对/api路径配置API安全中间件
app.MapWhen(context => context.Request.Path.StartsWithSegments("/api"), apiPipeline =>
{
    apiPipeline.UseSecurityHeaders(new SecurityHeaderOptions { IsApi = true });
    apiPipeline.UseRouting();
    apiPipeline.UseAuthorization();
    // 映射API端点(如果是控制器):apiPipeline.MapControllers();
});

// 针对非/api路径配置Razor Pages安全中间件
app.MapWhen(context => !context.Request.Path.StartsWithSegments("/api"), razorPipeline =>
{
    razorPipeline.UseSecurityHeaders(new SecurityHeaderOptions { IsApi = false });
    razorPipeline.UseRouting();
    razorPipeline.UseAuthorization();
    razorPipeline.MapRazorPages();
});

app.Run();

3. 在Razor视图中使用Nonce

在布局页或具体页面中,通过Context.Items获取nonce并应用到内联脚本:

<script nonce="@Context.Items["csp-nonce"]">
    // 内联脚本示例
    console.log("安全的内联脚本,使用CSP Nonce验证");
</script>

效果验证

  • API端点会返回API专用的CSP头,无nonce
  • Razor Pages会返回包含随机nonce的CSP头,且内联脚本可通过nonce验证正常执行
  • HttpContext.Items["csp-nonce"]可在Razor视图中正常获取

内容的提问来源于stack exchange,提问作者JED

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:57:19