如何不依赖庞大的Microsoft.Identity.Web包实现RequiredScope属性功能?
自定义实现.NET 8 API的范围授权(替代Microsoft.Identity.Web的RequiredScopeAttribute)
你完全可以自己实现轻量的范围授权逻辑,不需要引入Microsoft.Identity.Web的冗余依赖。以下是针对第三方IDP(如Amazon Cognito、Okta)的实现方案:
1. 定义Scope授权需求与处理程序
首先创建授权需求类承载需要验证的scope列表,再实现对应的授权处理逻辑:
自定义Scope授权需求
public class ScopeRequirement : IAuthorizationRequirement { public string[] RequiredScopes { get; } public ScopeRequirement(params string[] requiredScopes) { RequiredScopes = requiredScopes ?? throw new ArgumentNullException(nameof(requiredScopes)); } }
实现授权处理逻辑
这个类负责从用户的Claims中提取scope并验证是否符合要求:
public class ScopeAuthorizationHandler : AuthorizationHandler<ScopeRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ScopeRequirement requirement) { // 从Claims中获取scope声明,适配Cognito/Okta等常见IDP的声明类型 var scopeClaim = context.User.FindFirst(ClaimTypes.Scope) ?? context.User.FindFirst("scope"); if (scopeClaim == null) return Task.CompletedTask; var userScopes = scopeClaim.Value.Split(' ', StringSplitOptions.RemoveEmptyEntries); // 验证用户是否拥有所有必需的scope(可改为Any实现"或"逻辑) var meetsRequirement = requirement.RequiredScopes .All(requiredScope => userScopes.Contains(requiredScope, StringComparer.OrdinalIgnoreCase)); if (meetsRequirement) context.Succeed(requirement); return Task.CompletedTask; } }
2. 创建标记属性(替代RequiredScopeAttribute)
写一个继承自AuthorizeAttribute的特性,用来在控制器/方法上标记需要的scope:
public class RequiredScopeAttribute : AuthorizeAttribute { public RequiredScopeAttribute(params string[] scopes) { // 生成唯一策略名称,用于动态创建授权规则 Policy = $"ScopePolicy_{string.Join("_", scopes)}"; } }
3. 注册授权服务与动态策略提供器
为避免手动注册每个scope组合的策略,实现动态策略提供器自动生成对应规则:
动态策略提供器
public class DynamicScopePolicyProvider : DefaultAuthorizationPolicyProvider { private readonly AuthorizationOptions _options; public DynamicScopePolicyProvider(IOptions<AuthorizationOptions> options) : base(options) { _options = options.Value; } public override async Task<AuthorizationPolicy?> GetPolicyAsync(string policyName) { // 识别Scope策略前缀,动态生成授权规则 if (policyName.StartsWith("ScopePolicy_", StringComparison.OrdinalIgnoreCase)) { var scopeNames = policyName["ScopePolicy_".Length..].Split('_', StringSplitOptions.RemoveEmptyEntries); var policyBuilder = new AuthorizationPolicyBuilder(); policyBuilder.Requirements.Add(new ScopeRequirement(scopeNames)); return policyBuilder.Build(); } // 非Scope策略使用默认逻辑 return await base.GetPolicyAsync(policyName); } }
在Program.cs中注册服务
var builder = WebApplication.CreateBuilder(args); // 添加授权服务 builder.Services.AddAuthorization(); // 注册动态策略提供器和Scope授权处理程序 builder.Services.AddSingleton<IAuthorizationPolicyProvider, DynamicScopePolicyProvider>(); builder.Services.AddScoped<IAuthorizationHandler, ScopeAuthorizationHandler>(); // 其他服务注册 builder.Services.AddControllers(); var app = builder.Build(); // 中间件配置 app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
4. 在API中使用
直接在控制器或方法上标记[RequiredScope]属性即可:
[ApiController] [Route("api/orders")] public class OrdersController : ControllerBase { // 需要"orders:read"权限才能访问 [HttpGet] [RequiredScope("orders:read")] public IActionResult GetOrders() { return Ok(new List<object>()); } // 需要同时拥有"orders:read"和"orders:write"权限 [HttpPut("{id}")] [RequiredScope("orders:read", "orders:write")] public IActionResult UpdateOrder(int id, [FromBody] object order) { return Ok("Order updated"); } }
适配不同IDP的注意事项
- 部分IDP可能使用不同的scope声明类型,可在
ScopeAuthorizationHandler中增加对应类型的检查。 - 若需支持通配符(如
orders:*),可修改验证逻辑为StartsWith或正则匹配。 - 要实现"用户拥有任意一个必需scope即可访问",只需把处理程序中的
All改为Any。
内容的提问来源于stack exchange,提问作者Ankush Jain
相关产品推荐
相关产品推荐

