Policy-based Authorization 是一种将授权逻辑封装为可复用、可测试策略的细粒度控制方式,区别于基于角色或声明字符串匹配的粗粒度 Role/Claim 授权,支持复合条件(如“部门经理且入职满2年”)和运行时参数(如资源ID)动态校验。

Policy-based Authorization 是什么,和 Role/Claim 授权有什么区别
它不是基于角色(Role)或原始声明(Claim)字符串匹配的粗粒度控制,而是把授权逻辑封装成可复用、可测试、可组合的策略(AuthorizationPolicy),由一个或多个 IAuthorizationRequirement 和对应的 AuthorizationHandler 驱动。
比如“用户必须是部门经理且入职满2年”这种复合条件,用 RequireRole("Manager") + RequireClaim("YearsOfService", "2") 很难安全表达——前者是 OR 语义,后者无法做数值比较。Policy 就是为这类场景设计的。
常见错误现象:InvalidOperationException: No service for type 'Microsoft.AspNetCore.Authorization.IAuthorizationService' has been registered. —— 忘了在 Program.cs 调用 AddAuthorization()。
实操建议:
- 必须在
builder.Services.AddAuthorization()后注册自定义AuthorizationHandler(用AddScoped或AddTransient) - 策略名(如
"SeniorManager")要全局唯一,且不能含空格或特殊字符 - 不要在
HandleRequirementAsync中 throw 异常来“拒绝”,而应调用context.Fail()
如何定义一个带参数的授权策略(比如按资源ID动态检查)
Policy 本身不接收运行时参数(如 id),但可以通过 IAuthorizationRequirement 携带上下文信息,并在 handler 中结合 Resource(如 ActionContext 或 HttpContext)提取实际值。
典型使用场景:编辑文章时,只允许作者或管理员操作当前 Article.Id 对应的数据。
实操建议:
- 定义一个带 ID 的 requirement:
public class EditArticleRequirement : IAuthorizationRequirement { public int ArticleId { get; } ... } - handler 中从
context.Resource取ActionContext,再解析 route data 或 model binding 参数获取id - 策略注册时用
options.AddPolicy("EditOwnArticle", p => p.Requirements.Add(new EditArticleRequirement(0)))—— 这里的0占位无意义,真实 ID 来自 runtime - Controller 中用
[Authorize(Policy = "EditOwnArticle")],然后在 handler 里做实际比对
为什么 [Authorize(Policy = "...")] 不生效,或者 handler 根本没被调用
最常见原因是策略名拼写不一致,或 controller/action 上漏了 [Authorize] 特性(即使策略名正确,没加特性也不会触发任何 handler)。
其他高频原因:
-
AddAuthorization()调用位置错误(必须在AddControllersWithViews()之前) - handler 实现类没注册到 DI 容器(忘记
AddScoped)() - handler 继承了错误泛型类型,比如写成
AuthorizationHandler却忘了重写HandleRequirementAsync - 策略中添加了多个 requirement,但某个 handler 调用了
context.Fail()导致整个策略失败,而你只检查了其中一个 handler 的日志
调试技巧:在 handler 构造函数或 HandleRequirementAsync 开头加一行 Console.WriteLine($"Handling {requirement.GetType().Name}"),看是否被实例化。
如何在 Razor 页面或 View 中判断策略是否通过
Razor 页面不能直接用 [Authorize(Policy = "...")](那是 controller/action 级),但可以注入 IAuthorizationService 手动检查。
示例(Index.cshtml.cs):
public class IndexModel : PageModel
{
private readonly IAuthorizationService _authz;
public IndexModel(IAuthorizationService authz) => _authz = authz;
public async Task OnGetAsync()
{
var result = await _authz.AuthorizeAsync(User, "SeniorManager");
ViewData["CanManage"] = result.Succeeded;
}
}
View 中用:@if (ViewData["CanManage"] as bool? == true) { ... }
注意点:
-
AuthorizeAsync第二个参数支持string(策略名)或IAuthorizationRequirement实例 - 如果策略依赖
Resource(如 MVC context),在 Razor Page 中传this作为 resource 可能不安全;更稳妥的是只用于无上下文依赖的策略 - 避免在循环中反复调用
AuthorizeAsync,性能差;可提前计算并缓存结果
策略真正的复杂点不在定义,而在 requirement 如何与运行时资源绑定——这个衔接处没有框架自动帮你做,得自己从 context.Resource 里挖数据,而且不同宿主(MVC / Minimal API / Blazor)提供的 resource 类型还不一样。











