授权策略与自定义 Policy
大约 9 分钟约 2820 字
授权策略与自定义 Policy
简介
ASP.NET Core 授权系统基于策略(Policy)模型,将授权逻辑从业务代码中解耦。理解 Policy、Requirement、Handler 三者的关系,以及基于资源、基于 Claims 的授权实现,有助于构建灵活且可维护的权限系统。
特点
策略授权基础
Policy/Requirement/Handler
// 授权系统三层架构:
// Policy — 策略(包含一个或多个 Requirement)
// Requirement — 需求(数据类,表示需要满足的条件)
// Handler — 处理器(逻辑类,判断是否满足条件)
// 1. 定义 Requirement
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
}
// 2. 定义 Handler
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
MinimumAgeRequirement requirement)
{
// 从 Claims 中查找生日
var birthDateClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);
if (birthDateClaim != null)
{
var birthDate = DateTime.Parse(birthDateClaim.Value);
var age = DateTime.Today.Year - birthDate.Year;
if (birthDate > DateTime.Today.AddYears(-age)) age--;
if (age >= requirement.MinimumAge)
{
context.Succeed(requirement); // 满足条件
}
}
return Task.CompletedTask;
// 不调用 Succeed → 不满足条件
}
}
// 3. 注册策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AtLeast18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
options.AddPolicy("AdminOnly", policy =>
policy.RequireRole("Admin"));
options.AddPolicy("CanEditArticle", policy =>
policy.Requirements.Add(new ArticleEditRequirement()));
});
// 注册 Handler
builder.Services.AddScoped<IAuthorizationHandler, MinimumAgeHandler>();
// 4. 使用策略
[Authorize(Policy = "AtLeast18")]
app.MapGet("/adult-content", () => "Adult content");
[Authorize(Policy = "AdminOnly")]
app.MapGet("/admin", () => "Admin panel");内置策略构建器
builder.Services.AddAuthorization(options =>
{
// RequireClaim — 需要 Claim
options.AddPolicy("EmployeeOnly", policy =>
policy.RequireClaim("EmployeeNumber"));
// RequireRole — 需要角色
options.AddPolicy("ManagerOrAdmin", policy =>
policy.RequireRole("Manager", "Admin"));
// RequireUserName — 需要特定用户名
options.AddPolicy("SpecificUser", policy =>
policy.RequireUserName("admin@example.com"));
// RequireAssertion — 自定义断言
options.AddPolicy("BusinessHours", policy =>
policy.RequireAssertion(context =>
{
var hour = DateTime.Now.Hour;
return hour >= 9 && hour < 18;
}));
// 组合多个条件(AND 关系)
options.AddPolicy("CanAccessFinance", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Finance");
policy.RequireClaim("Department", "Finance");
policy.Requirements.Add(new MinimumAgeRequirement(18));
});
// 默认策略
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
// 回退策略(未指定策略时使用)
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});资源授权
基于具体资源的权限判断
// 资源授权:判断用户能否操作特定资源
// 例如:用户能否编辑某篇文章?
// 1. 定义 Requirement
public class ArticleOwnerRequirement : IAuthorizationRequirement { }
// 2. 定义 Handler(带资源类型)
public class ArticleOwnerHandler : AuthorizationHandler<ArticleOwnerRequirement, Article>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
ArticleOwnerRequirement requirement,
Article resource)
{
// 判断用户是否是文章作者或管理员
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == resource.AuthorId.ToString() ||
context.User.IsInRole("Admin"))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
// 3. 使用资源授权
app.MapPut("/articles/{id}", async (int id, Article update,
IAuthorizationService authService, AppDbContext db, HttpContext http) =>
{
var article = await db.Articles.FindAsync(id);
if (article == null) return Results.NotFound();
// 授权检查
var result = await authService.AuthorizeAsync(http.User, article, "ArticleOwner");
if (!result.Succeeded)
return Results.Forbid();
article.Title = update.Title;
article.Content = update.Content;
await db.SaveChangesAsync();
return Results.Ok(article);
});
// 4. 在 Controller 中使用
[Authorize]
public class ArticlesController : ControllerBase
{
private readonly IAuthorizationService _authService;
[HttpPut("{id}")]
public async Task<IActionResult> Update(int id, ArticleUpdateModel model)
{
var article = await _db.Articles.FindAsync(id);
var result = await _authService.AuthorizeAsync(User, article, "ArticleOwner");
if (!result.Succeeded) return Forbid();
// ...
return Ok();
}
}多 Handler 策略
一个 Requirement 多个 Handler
// 多个 Handler 处理同一个 Requirement
// 只要有一个 Succeed,Requirement 就满足(OR 关系)
// 场景:多种方式可以访问付费内容
public class PremiumContentRequirement : IAuthorizationRequirement { }
// Handler 1:VIP 用户
public class VipUserHandler : AuthorizationHandler<PremiumContentRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PremiumContentRequirement requirement)
{
if (context.User.HasClaim(c => c.Type == "VIP" && c.Value == "true"))
context.Succeed(requirement);
return Task.CompletedTask;
}
}
// Handler 2:付费用户
public class PaidUserHandler : AuthorizationHandler<PremiumContentRequirement>
{
private readonly AppDbContext _db;
public PaidUserHandler(AppDbContext db) => _db = db;
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PremiumContentRequirement requirement)
{
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId != null)
{
var hasPaid = await _db.Payments.AnyAsync(p =>
p.UserId == int.Parse(userId) && p.Expiry > DateTime.UtcNow);
if (hasPaid) context.Succeed(requirement);
}
}
}
// Handler 3:管理员
public class AdminHandler : AuthorizationHandler<PremiumContentRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PremiumContentRequirement requirement)
{
if (context.User.IsInRole("Admin"))
context.Succeed(requirement);
return Task.CompletedTask;
}
}
// 注册(全部注册即可,框架自动调用所有 Handler)
builder.Services.AddScoped<IAuthorizationHandler, VipUserHandler>();
builder.Services.AddScoped<IAuthorizationHandler, PaidUserHandler>();
builder.Services.AddScoped<IAuthorizationHandler, AdminHandler>();授权管道
AuthorizationMiddleware 流程
自定义授权策略提供者
动态策略加载
/// <summary>
/// 自定义策略提供者 — 从数据库动态加载策略
/// </summary>
public class DynamicAuthorizationPolicyProvider : IAuthorizationPolicyProvider
{
private readonly DefaultAuthorizationPolicyProvider _fallbackProvider;
private readonly IPermissionService _permissionService;
private readonly ConcurrentDictionary<string, AuthorizationPolicy> _policyCache = new();
public DynamicAuthorizationPolicyProvider(
IOptions<AuthorizationOptions> options,
IPermissionService permissionService)
{
_fallbackProvider = new DefaultAuthorizationPolicyProvider(options);
_permissionService = permissionService;
}
public async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
// 先从缓存中查找
if (_policyCache.TryGetValue(policyName, out var cachedPolicy))
return cachedPolicy;
// 检查是否是内置策略
if (policyName.StartsWith("Require"))
{
return await _fallbackProvider.GetPolicyAsync(policyName);
}
// 从数据库加载动态策略
var permissions = await _permissionService.GetPolicyPermissionsAsync(policyName);
if (permissions == null)
return null;
var policyBuilder = new AuthorizationPolicyBuilder();
policyBuilder.RequireAuthenticatedUser();
foreach (var permission in permissions)
{
policyBuilder.RequireClaim("permission", permission.Code);
}
var policy = policyBuilder.Build();
_policyCache[policyName] = policy;
return policy;
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
=> _fallbackProvider.GetDefaultPolicyAsync();
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
=> _fallbackProvider.GetFallbackPolicyAsync();
}
// 注册
builder.Services.AddSingleton<IAuthorizationPolicyProvider, DynamicAuthorizationPolicyProvider>();基于权限码的授权
/// <summary>
/// 基于权限码的细粒度授权系统
/// </summary>
public class PermissionRequirement : IAuthorizationRequirement
{
public string PermissionCode { get; }
public PermissionRequirement(string permissionCode) => PermissionCode = permissionCode;
}
public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IPermissionService _permissionService;
public PermissionHandler(IPermissionService permissionService)
{
_permissionService = permissionService;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null) return;
// 从数据库或缓存检查用户权限
var hasPermission = await _permissionService.HasPermissionAsync(
int.Parse(userId), requirement.PermissionCode);
if (hasPermission)
{
context.Succeed(requirement);
}
}
}
// 扩展方法简化使用
public static class AuthorizationExtensions
{
public static AuthorizationPolicyBuilder RequirePermission(
this AuthorizationPolicyBuilder builder, string permissionCode)
{
builder.Requirements.Add(new PermissionRequirement(permissionCode));
return builder;
}
}
// 配置策略
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CanCreateOrder", policy =>
policy.RequirePermission("orders.create"));
options.AddPolicy("CanDeleteOrder", policy =>
policy.RequirePermission("orders.delete"));
options.AddPolicy("CanViewFinance", policy =>
policy.RequirePermission("finance.view"));
});
// 使用
[Authorize(Policy = "CanCreateOrder")]
[HttpPost]
public IActionResult CreateOrder([FromBody] CreateOrderRequest request)
{
return Ok();
}多租户授权
租户隔离策略
/// <summary>
/// 多租户授权 — 确保用户只能访问自己租户的数据
/// </summary>
public class TenantRequirement : IAuthorizationRequirement { }
public class TenantHandler : AuthorizationHandler<TenantRequirement, ITenantResource>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context,
TenantRequirement requirement,
ITenantResource resource)
{
var userTenantId = context.User.FindFirst("tenant_id")?.Value;
if (userTenantId != null && userTenantId == resource.TenantId)
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
public interface ITenantResource
{
string TenantId { get; }
}
// 使用
app.MapGet("/api/orders/{id}", async (int id, IAuthorizationService auth, AppDbContext db, HttpContext http) =>
{
var order = await db.Orders.FindAsync(id);
if (order == null) return Results.NotFound();
var result = await auth.AuthorizeAsync(http.User, order, "Tenant");
if (!result.Succeeded)
return Results.Forbid();
return Results.Ok(order);
});
// 数据库级别自动过滤
public class TenantDbContext : DbContext
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantDbContext(DbContextOptions<TenantDbContext> options,
IHttpContextAccessor httpContextAccessor)
: base(options)
{
_httpContextAccessor = httpContextAccessor;
}
public string? CurrentTenantId =>
_httpContextAccessor.HttpContext?.User.FindFirst("tenant_id")?.Value;
public override async Task<int> SaveChangesAsync(CancellationToken ct = default)
{
// 自动设置租户 ID
foreach (var entry in ChangeTracker.Entries<ITenantResource>()
.Where(e => e.State == EntityState.Added))
{
entry.Entity.TenantId = CurrentTenantId;
}
return await base.SaveChangesAsync(ct);
}
// 全局查询过滤器
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().HasQueryFilter(o => o.TenantId == CurrentTenantId);
}
}授权性能优化
缓存与批量检查
/// <summary>
/// 授权缓存 — 避免每次请求查询数据库
/// </summary>
public class CachedPermissionHandler : AuthorizationHandler<PermissionRequirement>
{
private readonly IPermissionService _permissionService;
private readonly IDistributedCache _cache;
private readonly ILogger<CachedPermissionHandler> _logger;
public CachedPermissionHandler(
IPermissionService permissionService,
IDistributedCache cache,
ILogger<CachedPermissionHandler> logger)
{
_permissionService = permissionService;
_cache = cache;
_logger = logger;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null) return;
var cacheKey = $"user:perms:{userId}";
var cachedPerms = await _cache.GetStringAsync(cacheKey);
HashSet<string> permissions;
if (cachedPerms != null)
{
permissions = JsonSerializer.Deserialize<HashSet<string>>(cachedPerms)!;
}
else
{
permissions = await _permissionService.GetUserPermissionsAsync(int.Parse(userId));
await _cache.SetStringAsync(cacheKey,
JsonSerializer.Serialize(permissions),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
}
if (permissions.Contains(requirement.PermissionCode))
{
context.Succeed(requirement);
}
}
}
// 批量权限检查(一次请求检查多个权限)
public class BatchPermissionCheck
{
private readonly IAuthorizationService _authService;
private readonly ClaimsPrincipal _user;
public BatchPermissionCheck(IAuthorizationService authService, ClaimsPrincipal user)
{
_authService = authService;
_user = user;
}
public async Task<Dictionary<string, bool>> CheckAsync(params string[] permissionCodes)
{
var results = new Dictionary<string, bool>();
var tasks = permissionCodes.Select(async code =>
{
var result = await _authService.AuthorizeAsync(
_user, null, $"Permission:{code}");
results[code] = result.Succeeded;
});
await Task.WhenAll(tasks);
return results;
}
}
// 返回前端需要的权限矩阵
app.MapGet("/api/permissions/my", async (
IAuthorizationService auth,
HttpContext http,
IPermissionService permService) =>
{
var userId = http.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId == null) return Results.Unauthorized();
var allPermissions = await permService.GetAllPermissionCodesAsync();
var check = new BatchPermissionCheck(auth, http.User);
var myPermissions = await check.CheckAsync(allPermissions.ToArray());
return Results.Ok(myPermissions);
});// AuthorizationMiddleware 核心逻辑(简化)
public class AuthorizationMiddleware
{
private readonly RequestDelegate _next;
private readonly IAuthorizationPolicyProvider _policyProvider;
public async Task Invoke(HttpContext context)
{
// 从端点元数据获取授权信息
var endpoint = context.GetEndpoint();
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>();
if (authorizeData != null && authorizeData.Count > 0)
{
// 合并策略
var policy = await AuthorizationPolicy.CombineAsync(
_policyProvider, authorizeData);
var authService = context.RequestServices.GetRequiredService<IAuthorizationService>();
// 执行授权检查
var result = await authService.AuthorizeAsync(context.User, policy);
if (!result.Succeeded)
{
// 未认证 → 401
if (context.User.Identity?.IsAuthenticated != true)
{
await context.ChallengeAsync();
return;
}
// 已认证但无权限 → 403
await context.ForbidAsync();
return;
}
}
await _next(context);
}
}优点
缺点
总结
ASP.NET Core 授权基于 Policy → Requirement → Handler 三层模型。Policy 包含一个或多个 Requirement,每个 Requirement 可由多个 Handler 处理(OR 关系)。内置策略构建器提供 RequireRole、RequireClaim、RequireAssertion 等便捷方法。资源授权使用 IAuthorizationService.AuthorizeAsync(user, resource, policy) 传入具体资源。自定义 Handler 继承 AuthorizationHandler<TRequirement> 或 AuthorizationHandler<TRequirement, TResource>。AuthorizationMiddleware 从端点元数据获取策略并执行检查。
关键知识点
- 先分清这个主题位于请求链路、后台任务链路还是基础设施链路。
- 服务端主题通常不只关心功能正确,还关心稳定性、性能和可观测性。
- 任何框架能力都要结合配置、生命周期、异常传播和外部依赖一起看。
- 安全类主题的关键不只在认证成功,而在于权限边界、证书信任链和审计链路是否完整。
项目落地视角
- 画清请求进入、业务执行、外部调用、日志记录和错误返回的完整路径。
- 为关键链路补齐超时、重试、熔断、追踪和结构化日志。
- 把配置与敏感信息分离,并明确不同环境的差异来源。
- 明确令牌生命周期、刷新策略、作用域、Claims 和失败返回模型。
常见误区
- 只会堆中间件或组件,不知道它们在链路中的执行顺序。
- 忽略生命周期和线程池、连接池等运行时资源约束。
- 没有监控和测试就对性能或可靠性下结论。
- 只验证登录成功,不验证权限收敛和令牌失效场景。
进阶路线
- 继续向运行时行为、可观测性、发布治理和微服务协同深入。
- 把主题和数据库、缓存、消息队列、认证授权联动起来理解。
- 沉淀团队级模板,包括统一异常处理、配置约定和基础设施封装。
- 继续深入零信任、细粒度授权、证书自动化和密钥轮换。
适用场景
- 当你准备把《授权策略与自定义 Policy》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合 API 服务、后台任务、实时通信、认证授权和微服务协作场景。
- 当需求开始涉及稳定性、性能、可观测性和发布流程时,这类主题会成为基础设施能力。
落地建议
- 先定义请求链路与失败路径,再决定中间件、过滤器、服务边界和依赖方式。
- 为关键链路补日志、指标、追踪、超时与重试策略。
- 环境配置与敏感信息分离,避免把生产参数写死在代码或镜像里。
排错清单
- 先确认问题发生在路由、模型绑定、中间件、业务层还是基础设施层。
- 检查 DI 生命周期、配置来源、序列化规则和认证上下文。
- 查看线程池、连接池、缓存命中率和外部依赖超时。
复盘问题
- 如果把《授权策略与自定义 Policy》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《授权策略与自定义 Policy》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《授权策略与自定义 Policy》最大的收益和代价分别是什么?
