mcpField
D
dotnet-api-security

dotnet-api-security

github.com/wshaddix安全86140 次安装35 周前更新
代码仓库

关于 dotnet-api-security

面向 ASP.NET Core 开发者的 API 安全技能,涵盖身份验证、授权及常见防护配置,并提供实现要点与易错提醒。

主要功能

  1. 01配置 Identity 与身份验证端点
  2. 02集成 OAuth 2.0、OIDC 和 JWT
  3. 03实现 Passkey / WebAuthn 身份验证
  4. 04设置 CORS 与 CSP 策略
  5. 05配置请求速率限制

适用场景

  1. 01为 API 添加账号注册与登录
  2. 02接入外部身份提供商
  3. 03为 API 配置跨域与内容安全策略
  4. 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:

端点方法说明
/registerPOST创建新用户账户
/loginPOST进行身份验证并接收令牌
/refreshPOST刷新过期的访问令牌
/confirmEmailGET确认电子邮件地址
/manage/infoGET/POST获取/更新用户个人资料
/manage/2faPOST配置双重身份验证

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
通行密钥注册流程
  1. 客户端调用 /passkey/register/options 获取 PublicKeyCredentialCreationOptions 挑战
  2. 客户端使用 Web Authentication API(navigator.credentials.create)创建凭据
  3. 客户端将证明响应发送到 /passkey/register
  4. 服务端验证并存储凭据
通行密钥身份验证流程
  1. 客户端调用 /passkey/login/options 获取 PublicKeyCredentialRequestOptions 挑战
  2. 客户端使用 navigator.credentials.get 对挑战进行签名
  3. 客户端将断言响应发送到 /passkey/login
  4. 服务端验证断言并签发会话/令牌

主要优势: 无可供网络钓取的密码;服务端不存储凭据(仅存储公钥);内置防重放攻击能力。


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 常见陷阱

  1. 不要在生产环境的 CORS 策略中使用 AllowAnyOrigin() ——始终明确指定来源。有关 CORS 的安全影响,请参阅 [skill:dotnet-security-owasp]。
  2. 使用外部 OIDC 提供程序时,不要忘记设置 MapInboundClaims = false ——否则,声明类型会被重新映射为很长的 XML 命名空间 URI,导致角色和名称查找失败。
  3. 不要在源代码或 appsettings.json 中硬编码 JWT 签名密钥 ——开发时使用用户机密;生产环境使用环境变量或托管标识。请参阅 [skill:dotnet-secrets-management]。
  4. 不要将 ClockSkew 设为 TimeSpan.Zero ——令牌签发方与验证方之间的微小时间差异会导致误报 401 错误。请使用 1-2 分钟。
  5. 不要忘记中间件顺序 ——UseAuthentication() 必须在 UseAuthorization() 之前调用,且 UseCors() 必须在 UseAuthorization() 之前调用。
  6. 不要在生产环境中同时使用 AllowAnyMethod() 和 AllowAnyHeader() ——明确列出允许的方法和标头,以遵循最小权限原则。
  7. 不要跳过对身份验证端点的速率限制 ——/login 和 /register 是常见的暴力破解目标。应用速率限制以防止凭据填充攻击。
  8. 不要在身份验证路径中使用由异常驱动的拒绝方式 ——对于攻击者可控的输入,应使用防御性解析(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+ 共享框架中)

References

评论

支持 Markdown