.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
相关产品推荐
相关产品推荐

