关于 security-headers
面向 Razor Pages 应用的 .NET 安全配置技能,介绍 HTTP 安全标头的内置与自定义中间件、CSP 选项、按环境配置及标头测试。
主要功能
- 01内置及自定义标头中间件
- 02内容安全策略配置
- 03CSP 随机数与违规报告
- 04按环境和路由配置标头
- 05安全标头测试指南
适用场景
- 01为 Razor Pages 添加防护标头
- 02为内联脚本配置 CSP
- 03在安全审查中检查响应标头
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill security-headers安装后在 Claude Code 中输入/security-headers确认已加载。
详细介绍
你是一名资深 .NET 安全架构师。在 Razor Pages 应用中实现安全标头时,请采用这些模式,以防范 XSS、点击劫持和中间人攻击等常见 Web 漏洞。目标平台为 .NET 8+,并启用可空引用类型。
理由
安全标头是纵深防御的关键机制,可在不更改应用代码的情况下保护应用免受各种攻击。正确配置可防止 XSS、数据注入、点击劫持、MIME 嗅探和其他常见漏洞。所有现代浏览器都支持这些标头。
安全标头概览
| 标头 | 用途 | OWASP 类别 |
|---|---|---|
| Content-Security-Policy | 防止 XSS、数据注入 | A7 |
| Strict-Transport-Security | 强制使用 HTTPS 连接 | A2 |
| X-Frame-Options | 防止点击劫持 | A6 |
| X-Content-Type-Options | 防止 MIME 嗅探 | A6 |
| Referrer-Policy | 控制来源信息 | 隐私 |
| Permissions-Policy | 限制浏览器功能 | 隐私 |
| X-XSS-Protection | 旧版 XSS 防护 | A7 |
模式 1:内置安全标头中间件
ASP.NET Core 提供了用于常见安全标头的内置中间件。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// HSTS (only in production)
if (!app.Environment.IsDevelopment())
{
app.UseHsts(); // Adds Strict-Transport-Security header
}
// HTTPS Redirection
app.UseHttpsRedirection();
// Security headers middleware (built-in .NET 8+)
// AddHeader can be used for custom headers
模式 2:自定义安全标头中间件
如需全面控制,请创建自定义中间件。
public class SecurityHeadersMiddleware(RequestDelegate next)
{
public async Task Invoke(HttpContext context)
{
// Prevent MIME sniffing
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
// Prevent clickjacking
context.Response.Headers["X-Frame-Options"] = "DENY";
// Legacy XSS protection (redundant with CSP, but good for older browsers)
context.Response.Headers["X-XSS-Protection"] = "1; mode=block";
// Control referrer information
context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
// Permissions Policy (formerly Feature-Policy)
context.Response.Headers["Permissions-Policy"] =
"accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()";
await next(context);
}
}
// Extension method
public static class SecurityHeadersExtensions
{
public static IApplicationBuilder UseSecurityHeaders(this IApplicationBuilder app)
{
return app.UseMiddleware<SecurityHeadersMiddleware>();
}
}
注册
// Program.cs
var app = builder.Build();
app.UseSecurityHeaders(); // Add early in pipeline
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
模式 3:内容安全策略(CSP)
CSP 是防止 XSS 和数据注入攻击的最强大安全标头。
基本 CSP 配置
public class CspMiddleware(RequestDelegate next, ILogger<CspMiddleware> logger)
{
private const string CspHeaderName = "Content-Security-Policy";
public async Task Invoke(HttpContext context)
{
var csp = new StringBuilder();
// Default fallback
csp.Append("default-src 'self'; ");
// Scripts: self + inline (nonce) + specific external sources
csp.Append("script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://js.stripe.com; ");
// Styles: self + inline + external CDNs
csp.Append("style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com; ");
// Images: self + data URIs + external sources
csp.Append("img-src 'self' data: https: blob:; ");
// Fonts: self + Google Fonts
csp.Append("font-src 'self' https://fonts.gstatic.com; ");
// Connections (AJAX/WebSockets)
csp.Append("connect-src 'self' https://api.example.com wss://ws.example.com; ");
// Frames: only allow specific sources
csp.Append("frame-src 'self' https://js.stripe.com https://hooks.stripe.com; ");
// Form submissions
csp.Append("form-action 'self'; ");
// Base URI restrictions
csp.Append("base-uri 'self'; ");
// Prevent mixed content
csp.Append("upgrade-insecure-requests; ");
// Report violations (report-uri is deprecated, use report-to)
csp.Append("report-uri /api/csp-report; ");
context.Response.Headers[CspHeaderName] = csp.ToString();
await next(context);
}
}
使用 nonce 配置内联脚本的 CSP
public class CspNonceMiddleware(RequestDelegate next)
{
public static readonly string NonceKey = "CSP-Nonce";
public async Task Invoke(HttpContext context)
{
// Generate cryptographically secure nonce
var nonce = GenerateNonce();
// Store in HttpContext for use in views
context.Items[NonceKey] = nonce;
// Add nonce to CSP header
var csp = $"script-src 'nonce-{nonce}' 'self'; " +
$"style-src 'nonce-{nonce}' 'self'; " +
"default-src 'self';";
context.Response.Headers["Content-Security-Policy"] = csp;
await next(context);
}
private static string GenerateNonce()
{
var bytes = new byte[16];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(bytes);
return Convert.ToBase64String(bytes);
}
}
// Tag Helper for nonce
[HtmlTargetElement("script", Attributes = "asp-add-nonce")]
public class ScriptNonceTagHelper(IHttpContextAccessor httpContextAccessor) : TagHelper
{
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var nonce = httpContextAccessor.HttpContext?.Items[CspNonceMiddleware.NonceKey] as string;
if (!string.IsNullOrEmpty(nonce))
{
output.Attributes.SetAttribute("nonce", nonce);
}
}
}
// Usage in Razor view
<script asp-add-nonce>
console.log('This inline script is allowed because it has a nonce');
</script>
模式 4:可配置的安全标头
允许针对不同环境采用不同配置。
public class SecurityHeadersOptions
{
public bool UseStrictCsp { get; set; } = true;
public List<string> AllowedScriptSources { get; set; } = new() { "'self'" };
public List<string> AllowedStyleSources { get; set; } = new() { "'self'" };
public List<string> AllowedImageSources { get; set; } = new() { "'self'", "data:", "https:" };
public bool UpgradeInsecureRequests { get; set; } = true;
public string? ReportUri { get; set; }
}
public class ConfigurableSecurityHeadersMiddleware(RequestDelegate next, IOptions<SecurityHeadersOptions> options, ILogger<ConfigurableSecurityHeadersMiddleware> logger)
{
private readonly SecurityHeadersOptions _options = options.Value;
public async Task Invoke(HttpContext context)
{
// Standard security headers
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
context.Response.Headers["X-Frame-Options"] = "DENY";
context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
// Build CSP
var csp = new StringBuilder();
csp.Append($"default-src 'self'; ");
csp.Append($"script-src {string.Join(" ", _options.AllowedScriptSources)}; ");
csp.Append($"style-src {string.Join(" ", _options.AllowedStyleSources)}; ");
csp.Append($"img-src {string.Join(" ", _options.AllowedImageSources)}; ");
csp.Append("font-src 'self'; ");
csp.Append("connect-src 'self'; ");
csp.Append("form-action 'self'; ");
csp.Append("base-uri 'self'; ");
if (_options.UpgradeInsecureRequests)
{
csp.Append("upgrade-insecure-requests; ");
}
if (!string.IsNullOrEmpty(_options.ReportUri))
{
csp.Append($"report-uri {_options.ReportUri}; ");
}
context.Response.Headers["Content-Security-Policy"] = csp.ToString();
await next(context);
}
}
// Configuration in appsettings.json
{
"SecurityHeaders": {
"UseStrictCsp": true,
"AllowedScriptSources": ["'self'", "'unsafe-inline'", "https://cdn.jsdelivr.net"],
"AllowedStyleSources": ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
"UpgradeInsecureRequests": true,
"ReportUri": "/api/csp-report"
}
}
// Registration
builder.Services.Configure<SecurityHeadersOptions>(
builder.Configuration.GetSection("SecurityHeaders"));
模式 5:CSP 违规报告
public class CspReportRequest
{
[JsonPropertyName("csp-report")]
public CspReport? Report { get; set; }
}
public class CspReport
{
[JsonPropertyName("document-uri")]
public string? DocumentUri { get; set; }
[JsonPropertyName("referrer")]
public string? Referrer { get; set; }
[JsonPropertyName("violated-directive")]
public string? ViolatedDirective { get; set; }
[JsonPropertyName("effective-directive")]
public string? EffectiveDirective { get; set; }
[JsonPropertyName("blocked-uri")]
public string? BlockedUri { get; set; }
[JsonPropertyName("source-file")]
public string? SourceFile { get; set; }
}
public class CspReportHandler(ILogger<CspReportHandler> logger) : IRequestHandler<CspReportRequest>
{
public Task Handle(CspReportRequest request, CancellationToken cancellationToken)
{
var report = request.Report;
if (report != null)
{
logger.LogWarning(
"CSP Violation: {BlockedUri} blocked by {ViolatedDirective} on {DocumentUri}",
report.BlockedUri,
report.ViolatedDirective,
report.DocumentUri);
}
return Task.CompletedTask;
}
}
// Minimal API endpoint
app.MapPost("/api/csp-report", (CspReportRequest report, IMediator mediator) =>
{
// Process asynchronously
_ = mediator.Send(report);
return Results.Ok();
});
模式 6:HSTS 配置
// Program.cs
if (!app.Environment.IsDevelopment())
{
// Add HSTS middleware
app.UseHsts();
}
// Or configure explicitly
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365);
options.IncludeSubDomains = true;
options.Preload = true; // Submit to browser preload list
});
// HSTS can also be configured via web.config for IIS
HSTS 预加载注意事项
// Only enable preload after thorough testing
builder.Services.AddHsts(options =>
{
options.MaxAge = TimeSpan.FromDays(365 * 2); // Minimum 1 year for preload
options.IncludeSubDomains = true; // Required for preload
options.Preload = true;
});
// Before enabling preload:
// 1. Ensure HTTPS works correctly
// 2. Ensure all subdomains serve HTTPS
// 3. Test thoroughly
// 4. Submit to https://hstspreload.org/
模式 7:功能策略(Permissions Policy)
控制可使用哪些浏览器功能。
public class PermissionsPolicyMiddleware(RequestDelegate next)
{
public async Task Invoke(HttpContext context)
{
// Modern Permissions-Policy header
var permissions = new[]
{
"accelerometer=()",
"camera=()",
"geolocation=()",
"gyroscope=()",
"magnetometer=()",
"microphone=()",
"payment=()",
"usb=()",
"screen-wake-lock=()",
"xr-spatial-tracking=()",
"display-capture=()"
};
context.Response.Headers["Permissions-Policy"] = string.Join(", ", permissions);
await next(context);
}
}
// Allow specific features
// camera=(self "https://trusted-site.com")
// geolocation=(self)
模式 8:针对特定路由设置条件式标头
public class ConditionalSecurityHeadersMiddleware(RequestDelegate next)
{
public async Task Invoke(HttpContext context)
{
var path = context.Request.Path.Value?.ToLowerInvariant();
// Disable CSP for admin area (may use rich editors)
if (path?.StartsWith("/admin") == true)
{
context.Response.Headers["Content-Security-Policy"] = "default-src 'self' 'unsafe-inline' 'unsafe-eval';";
}
else
{
// Standard CSP
context.Response.Headers["Content-Security-Policy"] =
"default-src 'self'; script-src 'self'; style-src 'self';";
}
// Always add these
context.Response.Headers["X-Content-Type-Options"] = "nosniff";
context.Response.Headers["X-Frame-Options"] = "DENY";
await next(context);
}
}
模式 9:静态文件的安全标头
// Custom static file options with security headers
app.UseStaticFiles(new StaticFileOptions
{
OnPrepareResponse = ctx =>
{
// Add cache control for static assets
ctx.Context.Response.Headers["Cache-Control"] = "public, max-age=31536000, immutable";
// Ensure static files also get security headers
ctx.Context.Response.Headers["X-Content-Type-Options"] = "nosniff";
ctx.Context.Response.Headers["Referrer-Policy"] = "strict-origin-when-cross-origin";
}
});
反模式
过于宽松的 CSP
// ❌ BAD: Allowing everything defeats the purpose
csp.Append("default-src * 'unsafe-inline' 'unsafe-eval'; ");
// ✅ GOOD: Explicit allowlist
csp.Append("default-src 'self'; ");
csp.Append("script-src 'self' https://trusted-cdn.com; ");
生产环境中缺少 HSTS
// ❌ BAD: No HSTS in production
if (app.Environment.IsProduction())
{
// Missing HSTS!
}
// ✅ GOOD: HSTS always enabled in production
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
app.UseHttpsRedirection();
}
标头值不一致
// ❌ BAD: Conflicting frame options
context.Response.Headers["X-Frame-Options"] = "DENY";
csp.Append("frame-ancestors 'self'; "); // Conflicts with DENY
// ✅ GOOD: Consistent values
context.Response.Headers["X-Frame-Options"] = "SAMEORIGIN";
csp.Append("frame-ancestors 'self'; "); // Aligns with SAMEORIGIN
测试安全标头
# Using curl to check headers
curl -I https://your-site.com
# Using online scanners
# https://securityheaders.com/
# https://csp-evaluator.withgoogle.com/
# https://observatory.mozilla.org/
参考资料
- OWASP 安全标头项目:https://owasp.org/www-project-secure-headers/
- CSP 快速参考:https://content-security-policy.com/
- Mozilla MDN 安全标头:https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
- Google CSP 评估器:https://github.com/google/csp-evaluator
评论
支持 Markdown