如何自定义ASP.NET Core中带后缀参数的Authorize授权策略?
针对你的需求,我们可以通过自定义AuthorizeAttribute扩展 + 动态授权策略提供者的方式实现,既不用自定义Filter,也能在方法上直接指定后缀参数,同时兼容你已有的默认/回退策略,以及特殊组的全局权限逻辑。
1. 定义自定义授权需求(Requirement)
创建一个承载授权参数的需求类,包含后缀和特殊组配置:
public class DataAuthorizationRequirement : IAuthorizationRequirement { // 需要匹配的AD组后缀 public string RoleSuffix { get; } // 拥有全量权限的特殊组列表 public IEnumerable<string> SpecialGroups { get; } public DataAuthorizationRequirement(string roleSuffix, IEnumerable<string> specialGroups) { RoleSuffix = roleSuffix ?? throw new ArgumentNullException(nameof(roleSuffix)); SpecialGroups = specialGroups ?? Enumerable.Empty<string>(); } }
2. 自定义AuthorizeAttribute扩展
继承内置AuthorizeAttribute,添加RoleSuffix参数,并自动生成唯一策略标识:
public class DataAuthorizeAttribute : AuthorizeAttribute { public const string PolicyPrefix = "DataRoleSuffix_"; public string RoleSuffix { get => Policy.StartsWith(PolicyPrefix) ? Policy.Substring(PolicyPrefix.Length) : string.Empty; set => Policy = $"{PolicyPrefix}{value}"; } }
这样写[DataAuthorize(RoleSuffix = "_yyy")]时,会自动生成DataRoleSuffix__yyy的策略名,方便后续动态识别。
3. 实现动态授权策略提供者
通过IAuthorizationPolicyProvider动态生成对应后缀的授权策略,避免为每个后缀手动创建:
public class DataAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider { private readonly IConfiguration _configuration; private readonly AuthorizationOptions _options; public DataAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IConfiguration configuration) : base(options) { _options = options.Value; _configuration = configuration; } public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName) { // 优先使用默认策略(比如你的默认/回退策略) var policy = await base.GetPolicyAsync(policyName); // 识别自定义后缀策略并动态构建 if (policy == null && policyName.StartsWith(DataAuthorizeAttribute.PolicyPrefix)) { var roleSuffix = policyName.Substring(DataAuthorizeAttribute.PolicyPrefix.Length); // 从配置读取特殊组(可配置在appsettings.json) var specialGroups = _configuration.GetSection("Authorization:SpecialGroups").Get<string[]>(); policy = new AuthorizationPolicyBuilder() .AddRequirements(new DataAuthorizationRequirement(roleSuffix, specialGroups)) .Build(); // 缓存策略,避免重复创建 _options.AddPolicy(policyName, policy); } return policy; } }
4. 实现核心授权逻辑处理程序
编写AuthorizationHandler完成权限验证:
- 先检查用户是否属于特殊组,是则直接通过
- 否则验证用户AD组是否匹配指定后缀
public class DataAuthorizationHandler : AuthorizationHandler<DataAuthorizationRequirement> { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DataAuthorizationRequirement requirement) { // 获取用户的AD组声明(注意:ClaimType要和你的AD身份验证配置一致,比如"groups"或组SID对应的Type) var userGroups = context.User.Claims .Where(c => c.Type == "groups") .Select(c => c.Value); // 特殊组直接授权通过 if (requirement.SpecialGroups.Any(sg => userGroups.Contains(sg))) { context.Succeed(requirement); return Task.CompletedTask; } // 验证组后缀匹配 if (userGroups.Any(g => g.EndsWith(requirement.RoleSuffix, StringComparison.OrdinalIgnoreCase))) { context.Succeed(requirement); return Task.CompletedTask; } // 不满足条件则授权失败 context.Fail(); return Task.CompletedTask; } }
5. 配置服务(Program.cs/Startup.cs)
注册自定义服务,替换默认策略提供者,并保留你原有的默认/回退策略:
// .NET 6+ Program.cs示例 builder.Services.AddAuthorization(options => { // 保留你原有的默认和回退策略 options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddRequirements(new DataRequirement()) .Build(); options.FallbackPolicy = new AuthorizationPolicyBuilder() .AddRequirements(new DataRequirement()) .Build(); }); // 注册自定义授权处理程序 builder.Services.AddScoped<IAuthorizationHandler, DataAuthorizationHandler>(); // 替换默认策略提供者为自定义实现 builder.Services.AddSingleton<IAuthorizationPolicyProvider, DataAuthorizationPolicyProvider>();
6. 在控制器中使用
现在可以直接在方法上使用自定义属性指定后缀:
[ApiController] public class DataController : ControllerBase { // 回退策略(你的DataRequirement) public ActionResult Get1() {} // 默认策略(你的DataRequirement) [Authorize] public ActionResult Get2() {} // 自定义后缀授权 [DataAuthorize(RoleSuffix = "_yyy")] public ActionResult Get3() {} // 另一个后缀示例 [DataAuthorize(RoleSuffix = "_zzz")] public ActionResult Get4() {} }
额外补充:AD组Claim处理
如果你的AD组在Claims中仅存储SID而非名称,可以添加IClaimsTransformation将SID转换为组名称:
builder.Services.AddScoped<IClaimsTransformation, GroupNameClaimsTransformation>(); public class GroupNameClaimsTransformation : IClaimsTransformation { private readonly IAdGroupService _adGroupService; // 自定义AD组查询服务 public GroupNameClaimsTransformation(IAdGroupService adGroupService) { _adGroupService = adGroupService; } public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal) { var identity = (ClaimsIdentity)principal.Identity; var groupSids = identity.Claims .Where(c => c.Type == "groupsid") .Select(c => c.Value); foreach (var sid in groupSids) { var groupName = await _adGroupService.GetGroupNameBySidAsync(sid); if (!string.IsNullOrEmpty(groupName)) { identity.AddClaim(new Claim("groups", groupName)); } } return principal; } }
内容的提问来源于stack exchange,提问作者steb
相关产品推荐
相关产品推荐

