关于 dotnet-api-security
面向 ASP.NET Core 开发者的 API 安全技能,涵盖身份验证、授权及常见防护配置,并提供实现要点与易错提醒。
主要功能
- 01配置 Identity 与身份验证端点
- 02集成 OAuth 2.0、OIDC 和 JWT
- 03实现 Passkey / WebAuthn 身份验证
- 04设置 CORS 与 CSP 策略
- 05配置请求速率限制
适用场景
- 01为 API 添加账号注册与登录
- 02接入外部身份提供商
- 03为 API 配置跨域与内容安全策略
- 04限制登录及其他端点的请求频率
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ wshaddix/ dotnet-skills --skill dotnet-api-security安装后在 Claude Code 中输入/dotnet-api-security确认已加载。
详细介绍
dotnet-api-security
ASP.NET Core 的 API 级身份验证、授权和安全模式。此技能负责 API 身份验证实现:ASP.NET Core Identity 配置、OAuth 2.0/OIDC 集成、JWT bearer 令牌处理、通行密钥(WebAuthn)身份验证、CORS 策略、内容安全策略标头和速率限制。
身份验证职责: 此技能负责 API 级身份验证模式。Blazor 专属的身份验证 UI(AuthorizeView、CascadingAuthenticationState、客户端令牌处理)——相关技能推出后,请参阅 [skill:dotnet-blazor-auth]。OWASP 安全原则(跨领域漏洞缓解)——请参阅 [skill:dotnet-security-owasp]。
范围外内容: OWASP Top 10 缓解措施和已弃用的安全模式——请参阅 [skill:dotnet-security-owasp]。机密管理和安全配置——请参阅 [skill:dotnet-secrets-management]。加密算法选择——请参阅 [skill:dotnet-cryptography]。Blazor 身份验证 UI 组件——请参阅 [skill:dotnet-blazor-auth]。
交叉引用:[skill:dotnet-security-owasp] 介绍 OWASP 安全原则,[skill:dotnet-secrets-management] 介绍机密处理,[skill:dotnet-cryptography] 介绍加密最佳实践。
ASP.NET Core Identity
ASP.NET Core Identity 开箱即提供用户管理、密码哈希、基于角色的授权和双重身份验证。对于需要管理自身用户账户的应用程序,建议从此处开始。
builder.Services.AddIdentityApiEndpoints<ApplicationUser>(options =>
{
// Password requirements
options.Password.RequiredLength = 12;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = true;
options.Password.RequireDigit = true;
// Lockout
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.AllowedForNewUsers = true;
// User
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
var app = builder.Build();
app.MapIdentityApi<ApplicationUser>(); // Maps /register, /login, /refresh, /manage endpoints
Identity API 端点(.NET 8+)
MapIdentityApi<TUser>() 为 SPA 和移动客户端提供预构建的基于令牌的身份验证端点,无需 Razor UI:
| 端点 | 方法 | 说明 |
|---|---|---|
/register | POST | 创建新用户账户 |
/login | POST | 进行身份验证并接收令牌 |
/refresh | POST | 刷新过期的访问令牌 |
/confirmEmail | GET | 确认电子邮件地址 |
/manage/info | GET/POST | 获取/更新用户个人资料 |
/manage/2fa | POST | 配置双重身份验证 |
OAuth 2.0 / OpenID Connect
对于将身份验证委托给外部身份提供商(Entra ID、Auth0、Okta、Keycloak)的应用程序,请配置 OIDC 中间件。
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = builder.Configuration["Oidc:Authority"];
options.ClientId = builder.Configuration["Oidc:ClientId"];
options.ClientSecret = builder.Configuration["Oidc:ClientSecret"];
options.ResponseType = OpenIdConnectResponseType.Code; // Authorization Code Flow
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.MapInboundClaims = false; // Preserve original claim types
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "roles";
});
注意事项: MapInboundClaims = false 可防止 Microsoft OIDC 处理程序重新映射标准 JWT 声明(例如,将 sub 映射为 http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier)。将其设为 false 可保留身份提供商提供的原始声明类型。
JWT Bearer 令牌身份验证
对于客户端在 Authorization 标头中发送 JWT 的纯 API 场景:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = builder.Configuration["Jwt:Authority"];
options.Audience = builder.Configuration["Jwt:Audience"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(1) // Default is 5 min; tighten for security
};
});
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// Protect endpoints
app.MapGet("/api/profile", (ClaimsPrincipal user) =>
TypedResults.Ok(new { Name = user.Identity?.Name }))
.RequireAuthorization();
基于策略的授权
builder.Services.AddAuthorizationBuilder()
.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"))
.AddPolicy("PremiumUser", policy =>
policy.RequireClaim("subscription", "premium"))
.SetFallbackPolicy(new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build());
通行密钥 / WebAuthn(.NET 10)
.NET 10 引入了内置的通行密钥(WebAuthn/FIDO2)支持,可实现无密码身份验证。通行密钥使用公钥加密,并能抵御网络钓鱼。
// .NET 10: Add passkey support to Identity
builder.Services.AddIdentityApiEndpoints<ApplicationUser>(options =>
{
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders()
.AddPasskeys(); // Enable WebAuthn passkey authentication
var app = builder.Build();
app.MapIdentityApi<ApplicationUser>();
// Passkey registration and authentication endpoints are added automatically
通行密钥注册流程
- 客户端调用
/passkey/register/options获取PublicKeyCredentialCreationOptions挑战 - 客户端使用 Web Authentication API(
navigator.credentials.create)创建凭据 - 客户端将证明响应发送到
/passkey/register - 服务端验证并存储凭据
通行密钥身份验证流程
- 客户端调用
/passkey/login/options获取PublicKeyCredentialRequestOptions挑战 - 客户端使用
navigator.credentials.get对挑战进行签名 - 客户端将断言响应发送到
/passkey/login - 服务端验证断言并签发会话/令牌
主要优势: 无可供网络钓取的密码;服务端不存储凭据(仅存储公钥);内置防重放攻击能力。
CORS 策略
跨源资源共享(CORS)用于控制哪些源可以调用你的 API。始终使用显式命名的策略——切勿在生产环境中使用 AllowAnyOrigin()。
builder.Services.AddCors(options =>
{
options.AddPolicy("Production", policy =>
{
policy.WithOrigins(
"https://app.example.com",
"https://admin.example.com")
.WithMethods("GET", "POST", "PUT", "DELETE")
.WithHeaders("Content-Type", "Authorization")
.SetPreflightMaxAge(TimeSpan.FromMinutes(10)); // Cache preflight
});
options.AddPolicy("Development", policy =>
{
policy.WithOrigins("https://localhost:5173") // Vite dev server
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors(app.Environment.IsDevelopment() ? "Development" : "Production");
常见的 CORS 陷阱
AllowAnyOrigin()+AllowCredentials()会在运行时被 ASP.NET Core 拒绝。但SetIsOriginAllowed(_ => true)+AllowCredentials()会悄悄允许所有来源,切勿使用此模式。- **预检缓存:**如果不设置
SetPreflightMaxAge,浏览器会在每次跨源请求前发送 OPTIONS 请求。设置合理的缓存时长(10-60 分钟)以降低延迟。 - **携带凭据时使用通配符标头:**在 ASP.NET Core 中,
AllowAnyHeader()与AllowCredentials()组合使用虽然可行,但在某些浏览器中可能会出现意外行为。建议明确列出标头。 - **CORS 中间件顺序:**必须在
UseRouting()之后、UseAuthorization()之前调用UseCors()。
内容安全策略(CSP)
内容安全策略标头通过控制浏览器可加载的资源,防止 XSS、点击劫持和其他注入攻击。
app.Use(async (context, next) =>
{
// API-focused CSP -- restrict all content sources
context.Response.Headers.Append(
"Content-Security-Policy",
"default-src 'none'; frame-ancestors 'none'");
// Additional security headers
context.Response.Headers.Append("X-Content-Type-Options", "nosniff");
context.Response.Headers.Append("X-Frame-Options", "DENY");
context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin");
context.Response.Headers.Append("Permissions-Policy",
"camera=(), microphone=(), geolocation=()");
await next();
});
对于提供 HTML 响应的 API(Razor Pages、Blazor Server),请使用包含 nonce 的限制较宽松的 CSP:
app.Use(async (context, next) =>
{
var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16));
context.Items["CspNonce"] = nonce;
context.Response.Headers.Append(
"Content-Security-Policy",
$"default-src 'self'; script-src 'self' 'nonce-{nonce}'; style-src 'self' 'nonce-{nonce}'");
await next();
});
速率限制
ASP.NET Core 内置了速率限制中间件(Microsoft.AspNetCore.RateLimiting,.NET 7+)。提供四种算法:固定窗口、滑动窗口、令牌桶和并发限制器。
固定窗口
builder.Services.AddRateLimiter(options =>
{
options.AddFixedWindowLimiter("fixed", limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.QueueLimit = 0; // Reject immediately when limit reached
});
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
});
var app = builder.Build();
app.UseRateLimiter();
app.MapGet("/api/products", GetProducts)
.RequireRateLimiting("fixed");
滑动窗口
builder.Services.AddRateLimiter(options =>
{
options.AddSlidingWindowLimiter("sliding", limiterOptions =>
{
limiterOptions.PermitLimit = 100;
limiterOptions.Window = TimeSpan.FromMinutes(1);
limiterOptions.SegmentsPerWindow = 6; // 10-second segments
limiterOptions.QueueLimit = 0;
});
});
令牌桶
builder.Services.AddRateLimiter(options =>
{
options.AddTokenBucketLimiter("token", limiterOptions =>
{
limiterOptions.TokenLimit = 100;
limiterOptions.ReplenishmentPeriod = TimeSpan.FromSeconds(10);
limiterOptions.TokensPerPeriod = 10;
limiterOptions.QueueLimit = 0;
});
});
并发限制器
builder.Services.AddRateLimiter(options =>
{
options.AddConcurrencyLimiter("concurrent", limiterOptions =>
{
limiterOptions.PermitLimit = 10; // Max 10 concurrent requests
limiterOptions.QueueLimit = 5;
limiterOptions.QueueProcessingOrder = QueueProcessingOrder.OldestFirst;
});
});
按用户进行速率限制
builder.Services.AddRateLimiter(options =>
{
options.AddPolicy("per-user", httpContext =>
{
var userId = httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier)
?? httpContext.Connection.RemoteIpAddress?.ToString()
?? "anonymous";
return RateLimitPartition.GetFixedWindowLimiter(userId,
_ => new FixedWindowRateLimiterOptions
{
PermitLimit = 60,
Window = TimeSpan.FromMinutes(1)
});
});
});
**注意:**必须在 UseRouting() 之后、UseAuthorization() 和端点映射之前调用 UseRateLimiter(),才能正确应用速率限制。
Agent 常见陷阱
- 不要在生产环境的 CORS 策略中使用
AllowAnyOrigin()——始终明确指定来源。有关 CORS 的安全影响,请参阅 [skill:dotnet-security-owasp]。 - 使用外部 OIDC 提供程序时,不要忘记设置
MapInboundClaims = false——否则,声明类型会被重新映射为很长的 XML 命名空间 URI,导致角色和名称查找失败。 - 不要在源代码或
appsettings.json中硬编码 JWT 签名密钥 ——开发时使用用户机密;生产环境使用环境变量或托管标识。请参阅 [skill:dotnet-secrets-management]。 - 不要将
ClockSkew设为TimeSpan.Zero——令牌签发方与验证方之间的微小时间差异会导致误报 401 错误。请使用 1-2 分钟。 - 不要忘记中间件顺序 ——
UseAuthentication()必须在UseAuthorization()之前调用,且UseCors()必须在UseAuthorization()之前调用。 - 不要在生产环境中同时使用
AllowAnyMethod()和AllowAnyHeader()——明确列出允许的方法和标头,以遵循最小权限原则。 - 不要跳过对身份验证端点的速率限制 ——
/login和/register是常见的暴力破解目标。应用速率限制以防止凭据填充攻击。 - 不要在身份验证路径中使用由异常驱动的拒绝方式 ——对于攻击者可控的输入,应使用防御性解析(
TryFromBase64String、长度验证)。
前提条件
- .NET 8.0+(Identity API 端点、JWT bearer、CORS 和速率限制的 LTS 基线版本)
- 使用 passkey/WebAuthn 支持需要 .NET 10.0
- JWT bearer 身份验证需要
Microsoft.AspNetCore.Authentication.JwtBearer - OIDC 集成需要
Microsoft.AspNetCore.Authentication.OpenIdConnect Microsoft.AspNetCore.RateLimiting(包含在 .NET 7+ 共享框架中)