IAuthorizationRequirement是仅用于类型标记的空接口,要求不可变且通过构造函数传参;Handler必须继承AuthorizationHandler并重写HandleRequirementAsync,注册需为Scoped且策略名、类型、参数须完全匹配。

IAuthorizationRequirement 是个空接口,只起标记作用
它本身不带任何逻辑,纯粹用于类型区分——比如你定义 MinimumAgeRequirement 或 HasPermissionRequirement,都继承自它,目的是让 Handler 能通过泛型匹配到对应处理逻辑。不要试图在里面加属性或方法来“控制授权流程”,那是 Handler 的事。
常见错误:给 Requirement 加 public bool IsSatisfied { get; set; } 这类运行时状态字段。它会被反复实例化,状态无法保持,也违背了 Requirement 应该是不可变(immutable)设计原则。
正确做法是把必要参数全塞进构造函数,并设为只读:
public class MinimumAgeRequirement : IAuthorizationRequirement
{
public int MinimumAge { get; }
public MinimumAgeRequirement(int minimumAge) => MinimumAge = minimumAge;
}
Handler 必须继承 AuthorizationHandler 且重写 HandleRequirementAsync
这是真正干活的地方。ASP.NET Core 在授权时会查找所有注册的 AuthorizationHandler,并把当前 AuthorizationHandlerContext 和匹配的 Requirement 实例传进来。你得在这个方法里决定调用 context.Succeed(requirement) 还是 context.Fail()。
关键点:
- 必须用
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement) 签名,泛型参数 T 必须和你注册的 Requirement 类型一致
- 不能漏掉
context.Fail() —— 如果条件不满足又没调用 Fail,授权会“静默通过”(因为默认不失败)
- 不要在 Handler 里直接 throw 异常,授权失败走
Fail(),异常留给中间件层处理
- 如果需要访问用户信息,从
context.User 拿;要读 Claim,别手动遍历,用 context.User.FindFirst("age")?.Value 或 context.User.HasClaim()
注册 Requirement 和 Handler 要配对,且顺序影响行为
在 Program.cs(.NET 6+)中注册时,先用 AddAuthorization 添加策略,再用 AddScoped 注册 Handler。Requirement 不需要单独注册,但策略里要用到它:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("MinimumAge18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
builder.Services.AddScoped();
注意:
- 多个 Handler 可以处理同一个 Requirement,它们都会被执行(除非某个调用了
context.Succeed() 后你主动 return)
- 如果一个策略绑了多个 Requirement,所有对应 Handler 都必须成功,策略才算通过
- Handler 注册必须是
Scoped(不是 Singleton),因为 AuthorizationHandlerContext 是每次请求新建的
调试时最常卡在 Handler 没被触发
现象:加了断点但完全不进 HandleRequirementAsync,策略始终失败。大概率是下面几个原因:
- Handler 类型没注册,或者注册成了
AddSingleton 导致依赖注入失败
- 策略名拼错,比如控制器上写了
[Authorize(Policy = "MinAge18")],但注册的是 "MinimumAge18"
- Requirement 构造时传参出错,比如
new MinimumAgeRequirement(null) 导致构造函数抛异常,Handler 根本没创建出来
- .NET 版本差异:.NET 5+ 要求 Handler 必须实现
IAuthorizationHandler 接口(虽然继承 AuthorizationHandler 已隐式实现,但某些反射场景会校验)
建议加一行日志在 Handler 构造函数里,确认它是否被创建;再在 HandleRequirementAsync 开头打日志,看是否进入。比断点更可靠。
Requirement 和 Handler 的耦合其实很轻,但注册链上任意一环断开,整个授权就静默失效——这点最容易被忽略。
这是真正干活的地方。ASP.NET Core 在授权时会查找所有注册的 AuthorizationHandler,并把当前 AuthorizationHandlerContext 和匹配的 Requirement 实例传进来。你得在这个方法里决定调用 context.Succeed(requirement) 还是 context.Fail()。
关键点:
- 必须用
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumAgeRequirement requirement)签名,泛型参数 T 必须和你注册的 Requirement 类型一致 - 不能漏掉
context.Fail()—— 如果条件不满足又没调用 Fail,授权会“静默通过”(因为默认不失败) - 不要在 Handler 里直接 throw 异常,授权失败走
Fail(),异常留给中间件层处理 - 如果需要访问用户信息,从
context.User拿;要读 Claim,别手动遍历,用context.User.FindFirst("age")?.Value或context.User.HasClaim()
注册 Requirement 和 Handler 要配对,且顺序影响行为
在 Program.cs(.NET 6+)中注册时,先用 AddAuthorization 添加策略,再用 AddScoped 注册 Handler。Requirement 不需要单独注册,但策略里要用到它:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("MinimumAge18", policy =>
policy.Requirements.Add(new MinimumAgeRequirement(18)));
});
builder.Services.AddScoped();
注意:
- 多个 Handler 可以处理同一个 Requirement,它们都会被执行(除非某个调用了
context.Succeed()后你主动return) - 如果一个策略绑了多个 Requirement,所有对应 Handler 都必须成功,策略才算通过
- Handler 注册必须是
Scoped(不是 Singleton),因为AuthorizationHandlerContext是每次请求新建的
调试时最常卡在 Handler 没被触发
现象:加了断点但完全不进 HandleRequirementAsync,策略始终失败。大概率是下面几个原因:
- Handler 类型没注册,或者注册成了
AddSingleton导致依赖注入失败 - 策略名拼错,比如控制器上写了
[Authorize(Policy = "MinAge18")],但注册的是"MinimumAge18" - Requirement 构造时传参出错,比如
new MinimumAgeRequirement(null)导致构造函数抛异常,Handler 根本没创建出来 - .NET 版本差异:.NET 5+ 要求 Handler 必须实现
IAuthorizationHandler接口(虽然继承AuthorizationHandler已隐式实现,但某些反射场景会校验)
建议加一行日志在 Handler 构造函数里,确认它是否被创建;再在 HandleRequirementAsync 开头打日志,看是否进入。比断点更可靠。
Requirement 和 Handler 的耦合其实很轻,但注册链上任意一环断开,整个授权就静默失效——这点最容易被忽略。










