ASP.NET Core Web API如何实现OR逻辑的双JWT源授权策略?
问题
我有一个可接收两个JWT源令牌的ASP.NET Core Web API,需要定义授权策略满足:
- 第一个源的令牌必须具备指定角色才能通过
- 第二个源的任意令牌均可直接通过
我已经在builder.Services.AddAuthorization回调里分别实现了两个规则:
options.AddPolicy("HasTheRightRole", policy => policy.RequireRole("roleNameHere")); options.AddPolicy("HasTheSecondSource", policy => policy.RequireAuthenticatedUser().AddAuthenticationSchemes("secondSource"));
但用Combine组合这两个策略时是AND逻辑,不符合需求。我通过policy.RequireAssertion实现了OR逻辑:
options.AddPolicy("HasRoleOrPolicy", policy => { policy.RequireAssertion(context => context.User.IsInRole("roleNameHere") || context.User.HasClaim(claim => claim.Type == "iss" && claim.Value == "issuerOfSecondSource" && claim.Issuer == "issuerOfSecondSource")); });
想知道有没有更优的实现方式?
更优实现方式
方案1:利用多[Authorize]属性实现原生OR逻辑
ASP.NET Core支持在控制器/Action上叠加多个[Authorize]属性,默认逻辑是只要满足其中一个策略即可通过授权,可以直接复用你已定义的两个基础策略,无需额外编写复杂逻辑:
- 先完善并注册两个基础策略(给第二个策略补上iss断言,确保是指定源的令牌):
builder.Services.AddAuthorization(options => { options.AddPolicy("HasTheRightRole", policy => policy.RequireRole("roleNameHere")); options.AddPolicy("HasTheSecondSource", policy => policy.RequireAuthenticatedUser() .AddAuthenticationSchemes("secondSource") .RequireClaim("iss", "issuerOfSecondSource")); });
- 在需要授权的控制器或Action上叠加属性:
[Authorize(Policy = "HasTheRightRole")] [Authorize(Policy = "HasTheSecondSource")] public IActionResult ProtectedAction() { return Ok(); }
方案2:自定义OR授权需求与处理器(解耦逻辑)
如果需要将OR逻辑封装成可复用的策略,而非依赖属性叠加,可以自定义授权需求和处理器,更符合ASP.NET Core的授权扩展模式:
- 定义OR需求类:
public class OrAuthorizationRequirement : IAuthorizationRequirement { public IEnumerable<IAuthorizationRequirement> Requirements { get; } public OrAuthorizationRequirement(params IAuthorizationRequirement[] requirements) { Requirements = requirements; } }
- 编写对应的授权处理器:
public class OrAuthorizationHandler : AuthorizationHandler<OrAuthorizationRequirement> { private readonly IPolicyEvaluator _policyEvaluator; public OrAuthorizationHandler(IPolicyEvaluator policyEvaluator) { _policyEvaluator = policyEvaluator; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OrAuthorizationRequirement requirement) { foreach (var req in requirement.Requirements) { var childContext = new AuthorizationHandlerContext(new[] { req }, context.User, context.Resource); var result = await _policyEvaluator.EvaluateAsync(context.Policy, childContext, context.Resource); if (result.Succeeded) { context.Succeed(requirement); return; } } } }
- 注册处理器并创建组合策略:
// 注册自定义处理器 builder.Services.AddScoped<IAuthorizationHandler, OrAuthorizationHandler>(); builder.Services.AddAuthorization(options => { // 拆分单个需求 var roleRequirement = new RolesAuthorizationRequirement(new[] { "roleNameHere" }); var secondSourceRequirement = new ClaimsAuthorizationRequirement("iss", new[] { "issuerOfSecondSource" }); // 创建OR组合策略 options.AddPolicy("HasRoleOrSecondSource", policy => policy.Requirements.Add(new OrAuthorizationRequirement(roleRequirement, secondSourceRequirement))); });
方案3:简化现有RequireAssertion写法
如果不想引入复杂逻辑,也可以简化现有断言代码,让逻辑更清晰:
options.AddPolicy("HasRoleOrSecondSource", policy => { policy.RequireAssertion(context => context.User.IsInRole("roleNameHere") || context.User.HasClaim("iss", "issuerOfSecondSource")); });
注:
HasClaim(string type, string value)重载已能匹配iss声明的值,若需严格验证令牌的签发者(避免伪造iss声明),可保留原claim.Issuer的判断逻辑。
内容的提问来源于stack exchange,提问作者jason44107
相关产品推荐
相关产品推荐

